首页 文章 精选 留言 我的

精选列表

搜索[SpringBoot4],共10000篇文章
优秀的个人博客,低调大师

4种ThreadLocal你都知道吗?

点击蓝色字关注我们! 什么是ThreadLocal ThreadLocal类顾名思义可以理解为线程本地变量。也就是说如果定义了一个ThreadLocal, 每个线程往这个ThreadLocal中读写是线程隔离,互相之间不会影响的。它提供了一种将可变数据通过每个线程有自己的独立副本从而实现线程封闭的机制。 实际应用 实际开发中我们真正使用ThreadLocal的场景还是比较少的,大多数使用都是在框架里面。最常见的使用场景的话就是用它来解决数据库连接、Session管理等保证每一个线程中使用的数据库连接是同一个。还有一个用的比较多的场景就是用来解决SimpleDateFormat解决线程不安全的问题,不过现在java8提供了DateTimeFormatter它是线程安全的,感兴趣的同学可以去看看。还可以利用它进行优雅的传递参数,传递参数的时候,如果父线程生成的变量或者参数直接通过ThreadLocal传递到子线程参数就会丢失,这个后面会介绍一个其他的ThreadLocal来专门解决这个问题的。 ThreadLocal api介绍 ThreadLocal的API还是比较少的就几个api我们看下这几个api的使用,使用起来也超级简单 privatestaticThreadLocal<String>threadLocal=ThreadLocal.withInitial(()->"java金融");publicstaticvoidmain(String[]args){System.out.println("获取初始值:"+threadLocal.get());threadLocal.set("关注:【java金融】");System.out.println("获取修改后的值:"+threadLocal.get());threadLocal.remove();} 输出结果: 获取初始值:java金融获取修改后的值:关注:【java金融】 是不是炒鸡简单,就几行代码就把所有api都覆盖了。下面我们就来简单看看这几个api的源码吧。 成员变量 /**初始容量,必须为2的幂*Theinitialcapacity--MUSTbeapoweroftwo.*/privatestaticfinalintINITIAL_CAPACITY=16;/**Entry表,大小必须为2的幂*Thetable,resizedasnecessary.*table.lengthMUSTalwaysbeapoweroftwo.*/privateEntry[]table;/***Thenumberofentriesinthetable.*/privateintsize=0;/***Thenextsizevalueatwhichtoresize.*/privateintthreshold;//Defaultto0 这里会有一个面试经常问到的问题:为什么entry数组的大小,以及初始容量都必须是2的幂?对于 firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1); 以及很多源码里面都是使用 hashCode &( -1) 来代替hashCode% 。这种写法好处如下: 使用位运算替代取模,提升计算效率。 为了使不同 hash 值发生碰撞的概率更小,尽可能促使元素在哈希表中均匀地散列。 set方法 publicvoidset(Tvalue){Threadt=Thread.currentThread();ThreadLocalMapmap=getMap(t);if(map!=null)map.set(this,value);elsecreateMap(t,value);} set方法还是比较简单的,我们可以重点看下这个方法里面的ThreadLocalMap,它既然是个map(注意不要与java.util.map混为一谈,这里指的是概念上的map),肯定是有自己的key和value组成,我们根据源码可以看出它的key是其实可以把它简单看成是ThreadLocal,但是实际上ThreadLocal中存放的是ThreadLocal的弱引用,而它的value的话是我们实际set的值 staticclassEntryextendsWeakReference<ThreadLocal<?>>{/**ThevalueassociatedwiththisThreadLocal.*/Objectvalue;//实际存放的值Entry(ThreadLocal<?>k,Objectv){super(k);value=v;}} Entry就是是ThreadLocalMap里定义的节点,它继承了WeakReference类,定义了一个类型为Object的value,用于存放塞到ThreadLocal里的值。我们再来看下这个ThreadLocalMap是位于哪里的?我们看到ThreadLocalMap 是位于Thread里面的一个变量,而我们的值又是放在ThreadLocalMap,这样的话我们就实现了每个线程间的隔离。下面两张图的基本就把ThreadLocal的结构给介绍清楚了。接下来我们再看下ThreadLocalMap里面的数据结构,我们知道HaseMap解决hash冲突是由链表和红黑树(jdk1.8)来解决的,但是这个我们看到ThreadLocalMap只有一个数组,它是怎么来解决hash冲突呢?ThreadLocalMap采用「线性探测」的方式,什么是线性探测呢?就是根「据初始key的hashcode值确定元素在table数组中的位置,如果发现这个位置上已经有其他key值的元素被占用,则利用固定的算法寻找一定步长的下个位置,依次判断,直至找到能够存放的位置」。ThreadLocalMap解决Hash冲突的方式就是简单的步长加1或减1,寻找下一个相邻的位置。 /***Incrementimodulolen.*/privatestaticintnextIndex(inti,intlen){return((i+1<len)?i+1:0);}/***Decrementimodulolen.*/privatestaticintprevIndex(inti,intlen){return((i-1>=0)?i-1:len-1);} 这种方式的话如果一个线程里面有大量的ThreadLocal就会产生性能问题,因为每次都需要对这个table进行遍历,清空无效的值。所以我们在使用的时候尽可能的使用少的ThreadLocal,不要在线程里面创建大量的ThreadLocal,如果需要设置不同的参数类型我们可以通过ThreadLocal来存放一个Object的Map这样的话,可以大大减少创建ThreadLocal的数量。伪代码如下: publicfinalclassHttpContext{privateHttpContext(){}privatestaticfinalThreadLocal<Map<String,Object>>CONTEXT=ThreadLocal.withInitial(()->newConcurrentHashMap(64));publicstatic<T>voidadd(Stringkey,Tvalue){if(StringUtils.isEmpty(key)||Objects.isNull(value)){thrownewIllegalArgumentException("keyorvalueisnull");}CONTEXT.get().put(key,value);}publicstatic<T>Tget(Stringkey){return(T)get().get(key);}publicstaticMap<String,Object>get(){returnCONTEXT.get();}publicstaticvoidremove(){CONTEXT.remove();}} 这样的话我们如果需要传递不同的参数,可以直接使用一个ThreadLocal就可以代替多个ThreadLocal了。如果觉得不想这么玩,我就是要创建多个ThreadLocal,我的需求就是这样,而且性能还得要好,这个能不能实现列?可以使用netty的FastThreadLocal可以解决这个问题,不过要配合使FastThreadLocalThread或者它子类的线程线程效率才会更高,更多关于它的使用可以自行查阅资料哦。 下面我们先来看下它的这个哈希函数 //生成hash code间隙为这个魔数,可以让生成出来的值或者说ThreadLocal的ID较为均匀地分布在2的幂大小的数组中。privatestaticfinalintHASH_INCREMENT=0x61c88647;/***Returnsthenexthashcode.*/privatestaticintnextHashCode(){returnnextHashCode.getAndAdd(HASH_INCREMENT);} 可以看出,它是在上一个被构造出的ThreadLocal的ID/threadLocalHashCode的基础上加上一个魔数0x61c88647的。这个魔数的选取与斐波那契散列有关,0x61c88647对应的十进制为1640531527.当我们使用0x61c88647这个魔数累加对每个ThreadLocal分配各自的ID也就是threadLocalHashCode再与2的幂(数组的长度)取模,得到的结果分布很均匀。我们可以来也演示下通过这个魔数 publicclassMagicHashCode{privatestaticfinalintHASH_INCREMENT=0x61c88647;publicstaticvoidmain(String[]args){hashCode(16);//初始化16hashCode(32);//后续2倍扩容hashCode(64);}privatestaticvoidhashCode(Integerlength){inthashCode=0;for(inti=0;i<length;i++){hashCode=i*HASH_INCREMENT+HASH_INCREMENT;//每次递增HASH_INCREMENTSystem.out.print(hashCode&(length-1));System.out.print("");}System.out.println();}} 运行结果: 71451231018156134112907142128310172431613202729162330512192618152229411182507142128354249566361320273441485562512192633404754614111825323946536031017243138455259291623303744515818152229364350570 不得不佩服下这个作者,通过使用了斐波那契散列法,来保证哈希表的离散度,让结果很均匀。可见「代码要写的好,数学还是少不了」啊。其他的源码就不分析了,大家感兴趣可以自行去查看下。 ThreadLocal的内存泄露 关于ThreadLocal是否会引起内存泄漏也是一个比较有争议性的问题。首先我们需要知道什么是内存泄露? ❝ 在Java中,内存泄漏就是存在一些被分配的对象,这些对象有下面两个特点,首先,这些对象是可达的,即在有向图中,存在通路可以与其相连;其次,这些对象是无用的,即程序以后不会再使用这些对象。如果对象满足这两个条件,这些对象就可以判定为Java中的内存泄漏,这些对象不会被GC所回收,然而它却占用内存。 ❞ ThreadLocal的内存泄露情况: 线程的生命周期很长,当 ThreadLocal没有被外部强引用的时候就会被 GC回收(给 ThreadLocal置空了): ThreadLocalMap会出现一个 key为 null的 Entry,但这个 Entry的 value将永远没办法被访问到(后续在也无法操作 set、get等方法了)。如果当这个线程一直没有结束,那这个 key为 null的 Entry因为也存在强引用( Entry.value),而 Entry被当前线程的 ThreadLocalMap强引用( Entry[] table),导致这个 Entry.value永远无法被 GC,造成内存泄漏。下面我们来演示下这个场景 publicstaticvoidmain(String[]args)throwsInterruptedException{ThreadLocal<Long[]>threadLocal=newThreadLocal<>();for(inti=0;i<50;i++){run(threadLocal);}Thread.sleep(50000);//去除强引用threadLocal=null;System.gc();System.runFinalization();System.gc();}privatestaticvoidrun(ThreadLocal<Long[]>threadLocal){newThread(()->{threadLocal.set(newLong[1024*1024*10]);try{Thread.sleep(1000000000);}catch(InterruptedExceptione){e.printStackTrace();}}).start();} 通过jdk自带的工具jconsole.exe会发现即使执行了gc 内存也不会减少,因为key还被线程强引用着。效果图如下: 针对于这种情况 ThreadLocalMap在设计中,已经考虑到这种情况的发生,你只要调用 了set()、get()、remove()方法都会调用 cleanSomeSlots()、expungeStaleEntry()方法去清除 key为 null的 value。这是一种被动的清理方式,但是如果 ThreadLocal的 set(),get(),remove()方法没有被调用,就会导致 value的内存泄漏。它的文档推荐我们使用 static修饰的 ThreadLocal,导致 ThreadLocal的生命周期和持有它的类一样长,由于 ThreadLocal有强引用在,意味着这个 ThreadLocal不会被 GC。在这种情况下,我们如果不手动删除, Entry的 key永远不为 null,弱引用也就失去了意义。所以我们在使用的时候尽可能养成一个好的习惯,使用完成后手动调用下 remove方法。其实实际生产环境中我们手动 remove大多数情况并不是为了避免这种 key为 null的情况,更多的时候,是为了保证业务以及程序的正确性。比如我们下单请求后通过 ThreadLocal构建了订单的上下文请求信息,然后通过线程池异步去更新用户积分,这时候如果更新完成,没有进行 remove操作,即使下一次新的订单会覆盖原来的值但是也是有可能会导致业务问题。如果不想手动清理是否还有其他方式解决下列? FastThreadLocal 可以去了解下,它提供了自动回收机制。 在线程池的场景,程序不停止,线程一直在复用的话,基本不会销毁,其实本质就跟上面例子是一样的。如果线程不复用,用完就销毁了就不会存在泄露的情况。因为线程结束的时候会 jvm主动调用 exit方法清理。 /***ThismethodiscalledbythesystemtogiveaThread*achancetocleanupbeforeitactuallyexits.*/privatevoidexit(){if(group!=null){group.threadTerminated(this);group=null;}/*Aggressivelynulloutallreferencefields:seebug4006245*/target=null;/*Speedthereleaseofsomeoftheseresources*/threadLocals=null;inheritableThreadLocals=null;inheritedAccessControlContext=null;blocker=null;uncaughtExceptionHandler=null;} InheritableThreadLocal 文章开头有提到过父子之间线程的变量传递丢失的情况。但是InheritableThreadLocal提供了一种父子线程之间的数据共享机制。可以解决这个问题。 staticThreadLocal<String>threadLocal=newThreadLocal<>();staticInheritableThreadLocal<String>inheritableThreadLocal=newInheritableThreadLocal<>();publicstaticvoidmain(String[]args)throwsInterruptedException{threadLocal.set("threadLocal主线程的值");Thread.sleep(100);newThread(()->System.out.println("子线程获取threadLocal的主线程值:"+threadLocal.get())).start();Thread.sleep(100);inheritableThreadLocal.set("inheritableThreadLocal主线程的值");newThread(()->System.out.println("子线程获取inheritableThreadLocal的主线程值:"+inheritableThreadLocal.get())).start();} 输出结果 线程获取threadLocal的主线程值:null子线程获取inheritableThreadLocal的主线程值:inheritableThreadLocal主线程的值 但是InheritableThreadLocal和线程池使用的时候就会存在问题,因为子线程只有在线程对象创建的时候才会把父线程inheritableThreadLocals中的数据复制到自己的inheritableThreadLocals中。这样就实现了父线程和子线程的上下文传递。但是线程池的话,线程会复用,所以会存在问题。如果要解决这个问题可以有什么办法列?大家可以思考下,或者在下方留言哦。如果实在不想思考的话,可以参考下阿里巴巴的transmittable-thread-local哦。 总结 大概介绍了 ThreadLocal的常见用法,以及大致实现原理,以及关于 ThreadLocal的内存泄露问题,以及关于使用它需要注意的事项,以及如何解决父子线程之间的传递。介绍了 ThreadLocal、InheritableThreadLocal、FastThreadLocal、transmittable-thread-local各种使用场景,以及需要注意的事项。本文重点介绍了 ThreadLocal,如果把这个弄清楚了,其他几种ThreadLocal就更好理解了。 结束 由于自己才疏学浅,难免会有纰漏,假如你发现了错误的地方,还望留言给我指出来,我会对其加以修正。 如果你觉得文章还不错,你的转发、分享、赞赏、点赞、留言就是对我最大的鼓励。 感谢您的阅读,十分欢迎并感谢您的关注。 巨人的肩膀摘苹果: https://zhuanlan.zhihu.com/p/40515974 https://www.cnblogs.com/aspirant/p/8991010.html https://www.cnblogs.com/jiangxinlingdu/p/11123538.html https://blog.csdn.net/hewenbo111/article/details/80487252 最近面试BAT,整理一份面试资料《Java面试BATJ通关手册》,覆盖了Java核心技术、JVM、Java并发、SSM、微服务、数据库、数据结构、等等。获取方式:点“在看”,关注公众号并回复 666领取,更多内容陆续奉上。文章有帮助的话,在看,转发吧。谢谢支持哟 (*^__^*) 本文分享自微信公众号 - java金融(java4299)。如有侵权,请联系 support@oschina.cn 删除。本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

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

4. InfluxDB使用HTTP的API查询数据

参考官方开源文档 使用HTTP API查询数据https://docs.influxdata.com/influxdb/v1.7/guides/querying_data/ 使用HTTP的API查询数据 HTTP API是在InfluxDB中查询数据的主要方法(有关查询数据库的其他方法,请参阅命令行界面和客户端库)。 注意:以下示例使用curl命令行工具,该工具使用URL传输数据。学习的基础知识curl与HTTP脚本指南。 API查询语句 查询语句如下:curl -G 'http://localhost:8086/query?pretty=true' --data-urlencode "db=testdb" --data-urlencode "q=SELECT \"value\" FROM \"cpu_load_short\" WHERE \"region\"='us-west'" 在前面的篇章中,我已经创建了testdb数据库,以及插入了数据。 首先查看一下当前InfluxDB中的数据,如下: > show databasesname: databasesname----_internalmydbtestdb> > use testdbUsing database testdb> > show measurementsname: measurementsname----cpu_load_shorttobeornottobe> > select * from cpu_load_shortname: cpu_load_shorttime direction host region value---- --------- ---- ------ -----1422568543702900257 in server01 us-west 21422568543702900257 server02 us-west 0.551434055562000000000 server01 us-west 0.641546849598178339889 server02 0.671546850175491084332 server02 0.671546850460880063366 server02 0.67> > select * from cpu_load_short where region = 'us-west'name: cpu_load_shorttime direction host region value---- --------- ---- ------ -----1422568543702900257 in server01 us-west 21422568543702900257 server02 us-west 0.551434055562000000000 server01 us-west 0.64> 下面使用API请求查询如下: [root@server81 ~]# curl -G 'http://localhost:8086/query?pretty=true' --data-urlencode "db=testdb" --data-urlencode "q=SELECT \"value\" FROM \"cpu_load_short\" WHERE \"region\"='us-west'"{ "results": [ { "statement_id": 0, "series": [ { "name": "cpu_load_short", "columns": [ "time", "value" ], "values": [ [ "2015-01-29T21:55:43.702900257Z", 2 ], [ "2015-01-29T21:55:43.702900257Z", 0.55 ], [ "2015-06-11T20:46:02Z", 0.64 ] ] } ] } ]}[root@server81 ~]# 可以从上面看出,可以正确查询出条件为region = 'us-west' 的三条数据。 InfluxDB返回数据的格式是JSON格式。查询结果显示在"results"数组中。如果发生错误,InfluxDB会设置一个"error"带有错误解释。 例如查询region = 'us-south' 这个在数据中是没有的。下面执行看看: [root@server81 ~]# curl -G 'http://localhost:8086/query?pretty=true' --data-urlencode "db=testdb" --data-urlencode "q=SELECT \"value\" FROM \"cpu_load_short\" WHERE \"region\"='us-south'"{ "results": [ { "statement_id": 0 } ]}[root@server81 ~]# 注意:附加pretty=true到URL可以启用漂亮的JSON输出。虽然这对于调试或直接使用类似工具查询很有用curl,但不建议将其用于生产,因为它会消耗不必要的网络带宽。 如果没有pretty=true,那么执行会是怎么样的结果呢?执行如下: [root@server81 ~]# curl -G 'http://localhost:8086/query' --data-urlencode "db=testdb" --data-urlencode "q=SELECT \"value\" FROM \"cpu_load_short\" WHERE \"region\"='us-west'"{"results":[{"statement_id":0,"series":[{"name":"cpu_load_short","columns":["time","value"],"values":[["2015-01-29T21:55:43.702900257Z",2],["2015-01-29T21:55:43.702900257Z",0.55],["2015-06-11T20:46:02Z",0.64]]}]}]}[root@server81 ~]# API进行多个查询语句 在单个API调用中向InfluxDB发送多个查询。只需使用分号分隔每个查询,例如:curl -G 'http://localhost:8086/query?pretty=true' --data-urlencode "db=testdb" --data-urlencode "q=SELECT \"value\" FROM \"cpu_load_short\" WHERE \"region\"='us-west';SELECT count(\"value\") FROM \"cpu_load_short\" WHERE \"region\"='us-west'"执行请求如下: [root@server81 ~]# curl -G 'http://localhost:8086/query?pretty=true' --data-urlencode "db=testdb" --data-urlencode "q=SELECT \"value\" FROM \"cpu_load_short\" WHERE \"region\"='us-west';SELECT count(\"value\") FROM \"cpu_load_short\" WHERE \"region\"='us-west'"{ "results": [ { "statement_id": 0, "series": [ { "name": "cpu_load_short", "columns": [ "time", "value" ], "values": [ [ "2015-01-29T21:55:43.702900257Z", 2 ], [ "2015-01-29T21:55:43.702900257Z", 0.55 ], [ "2015-06-11T20:46:02Z", 0.64 ] ] } ] }, { "statement_id": 1, "series": [ { "name": "cpu_load_short", "columns": [ "time", "count" ], "values": [ [ "1970-01-01T00:00:00Z", 3 ] ] } ] } ]}[root@server81 ~]# 可以从上面的返回结果看出,两个查询语句的结果都会合并到result数组中返回。 查询数据的其他选项 设置时间戳格式 curl -G 'http://localhost:8086/query?pretty=true' --data-urlencode "db=testdb" --data-urlencode "epoch=s" --data-urlencode "q=SELECT \"value\" FROM \"cpu_load_short\" WHERE \"region\"='us-west'" 下面对比查看两个请求,一个有写--data-urlencode "epoch=s"条件,第二个没有该条件,分析一下返回结果中的时间格式。 [root@server81 ~]# curl -G 'http://localhost:8086/query?pretty=true' --data-urlencode "db=testdb" --data-urlencode "epoch=s" --data-urlencode "q=SELECT \"value\" FROM \"cpu_load_short\" WHERE \"region\"='us-west'"{ "results": [ { "statement_id": 0, "series": [ { "name": "cpu_load_short", "columns": [ "time", "value" ], "values": [ [ 1422568543, 2 ], [ 1422568543, 0.55 ], [ 1434055562, 0.64 ] ] } ] } ]}[root@server81 ~]# [root@server81 ~]# curl -G 'http://localhost:8086/query?pretty=true' --data-urlencode "db=testdb" --data-urlencode "q=SELECT \"value\" FROM \"cpu_load_short\" WHERE \"region\"='us-west'"{ "results": [ { "statement_id": 0, "series": [ { "name": "cpu_load_short", "columns": [ "time", "value" ], "values": [ [ "2015-01-29T21:55:43.702900257Z", 2 ], [ "2015-01-29T21:55:43.702900257Z", 0.55 ], [ "2015-06-11T20:46:02Z", 0.64 ] ] } ] } ]}[root@server81 ~]# 可以从上面的格式中看出,第一个查询出来的时间戳为1422568543,第二个查询出来的时间戳是2015-01-29T21:55:43.702900257Z。 关于查询是2015-01-29T21:55:43.702900257Z的解释:InfluxDB中的所有内容都以UTC格式存储和报告。默认情况下,时间戳以RFC3339 UTC返回,并具有纳秒级精度。 关于查询是1422568543的解释:设置epoch参数,则可以设置返回的时间戳格式,格式为epoch=[h,m,s,ms,u,ns]。 例如,设置秒级的时间戳则是epoch=s。 那么如果查询毫秒(ms)级别的呢?下面来查询看看:curl -G 'http://localhost:8086/query?pretty=true' --data-urlencode "db=testdb" --data-urlencode "epoch=ms" --data-urlencode "q=SELECT \"value\" FROM \"cpu_load_short\" WHERE \"region\"='us-west'" [root@server81 ~]# curl -G 'http://localhost:8086/query?pretty=true' --data-urlencode "db=testdb" --data-urlencode "epoch=ms" --data-urlencode "q=SELECT \"value\" FROM \"cpu_load_short\" WHERE \"region\"='us-west'"{ "results": [ { "statement_id": 0, "series": [ { "name": "cpu_load_short", "columns": [ "time", "value" ], "values": [ [ 1422568543702, 2 ], [ 1422568543702, 0.55 ], [ 1434055562000, 0.64 ] ] } ] } ]}[root@server81 ~]# 秒级的时间戳:1422568543 毫秒级的时间戳:1422568543702 可以看出精度越来越高,多了最后的702三位。 最大行限制 该max-row-limit配置选项允许用户限制返回结果的最大数量,以防止InfluxDB运行内存溢出。默认情况下,max-row-limit配置选项设置为0。该默认设置允许每个请求返回无限数量的行。 最大行限制仅适用于非分块查询。分块查询可以返回无限数量的点。 分块 通过设置chunked=true查询字符串参数,可以使用分块返回结果。 下面使用实操来演示一下分块的返回效果,首先查询一下数据如下:curl -G 'http://localhost:8086/query?pretty=true' --data-urlencode "db=testdb" --data-urlencode "epoch=ms" --data-urlencode "q=SELECT \"value\" FROM \"cpu_load_short\"" [root@server81 ~]# curl -G 'http://localhost:8086/query?pretty=true' --data-urlencode "db=testdb" --data-urlencode "epoch=ms" --data-urlencode "q=SELECT \"value\" FROM \"cpu_load_short\""{ "results": [ { "statement_id": 0, "series": [ { "name": "cpu_load_short", "columns": [ "time", "value" ], "values": [ [ 1422568543702, 2 ], [ 1422568543702, 0.55 ], [ 1434055562000, 0.64 ], [ 1546849598178, 0.67 ], [ 1546850175491, 0.67 ], [ 1546850460880, 0.67 ] ] } ] } ]}[root@server81 ~]# 可以从返回的结果看出,所有查询的结果都是写入一个result数组里面的。能否分成多个result数组返回结果呢? 下面来设置分块参数执行语句如下: 设置参数--data-urlencode "chunked=true" --data-urlencode "chunk_size=1"一条数据为一个返回result数组。 整体语句如下: curl -G 'http://localhost:8086/query?pretty=true' --data-urlencode "db=testdb" --data-urlencode "epoch=ms" --data-urlencode "chunked=true" --data-urlencode "chunk_size=1" --data-urlencode "q=SELECT \"value\" FROM \"cpu_load_short\"" [root@server81 ~]# curl -G 'http://localhost:8086/query?pretty=true' --data-urlencode "db=testdb" --data-urlencode "epoch=ms" --data-urlencode "chunked=true" --data-urlencode "chunk_size=1" --data-urlencode "q=SELECT \"value\" FROM \"cpu_load_short\""{ "results": [ { "statement_id": 0, "series": [ { "name": "cpu_load_short", "columns": [ "time", "value" ], "values": [ [ 1422568543702, 2 ] ], "partial": true } ], "partial": true } ]}{ "results": [ { "statement_id": 0, "series": [ { "name": "cpu_load_short", "columns": [ "time", "value" ], "values": [ [ 1422568543702, 0.55 ] ], "partial": true } ], "partial": true } ]}{ "results": [ { "statement_id": 0, "series": [ { "name": "cpu_load_short", "columns": [ "time", "value" ], "values": [ [ 1434055562000, 0.64 ] ], "partial": true } ], "partial": true } ]}{ "results": [ { "statement_id": 0, "series": [ { "name": "cpu_load_short", "columns": [ "time", "value" ], "values": [ [ 1546849598178, 0.67 ] ], "partial": true } ], "partial": true } ]}{ "results": [ { "statement_id": 0, "series": [ { "name": "cpu_load_short", "columns": [ "time", "value" ], "values": [ [ 1546850175491, 0.67 ] ], "partial": true } ], "partial": true } ]}{ "results": [ { "statement_id": 0, "series": [ { "name": "cpu_load_short", "columns": [ "time", "value" ], "values": [ [ 1546850460880, 0.67 ] ] } ] } ]}[root@server81 ~]# 好了,从上面可以看出,已经将每一条查询结果都分块到各自的result数组之中了。 本文分享自微信公众号 - DevOps社群(DevOpsCommunity)。如有侵权,请联系 support@oschina.cn 删除。本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

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

Apache IoTDB 系列教程-4:客户端接口

说了半天语法和部署运维,实际使用还是要落到代码里的,今天介绍一下客户端的接口。 正文 3516 字,预计阅读时间 5 分钟。 现在的客户端和服务器通信采用了跨语言的 RPC 框架 Thirft,理论上 Thrift 能生成的语言都能支持。但是直接用 Thrift 生成的代码对数据库使用者不太友好,所以我们在生成代码的基础上,包装出来了我们的各种客户端接口,这种接口对用户就比较友好了。接下来介绍一下各种客户端接口。 JDBC 接口 JDBC 是关系数据库的标准接口,也是大家最熟悉的接口。所以一开始我们就提供了这种接口。 和标准 JDBC 的使用方式一样,需要加载数据库驱动类,建立连接,建立 Statement,通过 Statement 执行语句,对于非查询来说,可以批量执行减少网络传输次数。下面是一个简单的例子, publicstaticvoidmain(String[]args)throwsClassNotFoundException,SQLException{ Class.forName("org.apache.iotdb.jdbc.IoTDBDriver"); try (Connection connection = DriverManager .getConnection("jdbc:iotdb://127.0.0.1:6667/", "root", "root"); Statement statement = connection.createStatement()) { // 创建存储组 statement.execute("SET STORAGE GROUP TO root.sg1"); // 创建时间序列 statement.execute("CREATE TIMESERIES root.sg1.d1.s1 WITH DATATYPE=INT64, ENCODING=RLE, COMPRESSOR=SNAPPY"); statement.execute("CREATE TIMESERIES root.sg1.d1.s2 WITH DATATYPE=INT64, ENCODING=RLE, COMPRESSOR=SNAPPY"); statement.execute("CREATE TIMESERIES root.sg1.d1.s3 WITH DATATYPE=INT64, ENCODING=RLE, COMPRESSOR=SNAPPY"); // 在客户端积累一批更新语句 for (int i = 0; i <= 100; i++) { statement.addBatch("insert into root.sg1.d1(timestamp, s1, s2, s3) values("+ i + "," + 1 + "," + 1 + "," + 1 + ")"); } // 执行 statement.executeBatch(); statement.clearBatch(); // 查询 ResultSet resultSet = statement.executeQuery("select * from root where time <= 10"); // 打印结果集 ResultSetMetaData metaData = resultSet.getMetaData(); int columnCount = metaData.getColumnCount(); while (resultSet.next()) { for (int i = 1; i < columnCount; i++) { System.out.print(resultSet.getString(i)); System.out.print(" "); } System.out.println(); } } } 完整的示例代码位置: https://github.com/apache/incubator-iotdb/blob/master/example/jdbc/src/main/java/org/apache/iotdb/JDBCExample.java Java 原生接口 Session 对于数据写入,SQL 解析就占了 70% 耗时。于是我们提供了一个原生的 NoSQL 接口(Session),相比于 JDBC 更高效。 insertRecord(String deviceId, long time, List<String> measurements, List<TSDataType>types,List<Object>values) 这个接口就对应一个 insert 语句,一次可以写入一个设备一个时间戳多个测点的值,其中值的类型需要和注册的类型保持一致,如果没注册过则自动注册此类型。 insertRecord(String deviceId, long time, List<String> measurements, List<String> values) 在一些场景下,客户端拿不到具体的数据类型,这时候可以用这种 String 参数的接口。如果提前注册了序列,服务器会根据注册的类型来解析这些 String 的值,如果没注册,会根据值的格式推断类型进行注册。 insertTablet(Tablet tablet, boolean sorted) 一个Tablet 是一个设备多个时间戳多个测点的值。这里要注意,每个测点在每个时间戳都需要有值,不能有空的。sorted 表示是否时间戳是递增的,如果能保证递增,可以设置为 true,否则我们还会再排个序。 如果只计算执行时间,这个接口是最高效的,因为里边使用了原始类型的数组,避免了装箱。但是,这个接口对数据的格式要求很高,如果数据采集不是对齐采的,强行转化成这种接口,转化的耗时需要统计一下。 此外还有 insertTablets 和 insertRecords 两种,其实就是以上几种接口的批量的形式。 Session 的查询结果集是 SessionDataSet,这个结构提供的 hasNext 和 next 方法把每一行数据都转化成了 RowRecord 这个结构,如果客户端还需要做其他转化,这个结构就多余了。这时候可以通过 SessionDataSet.iterator()得到一个迭代器,这个迭代器的访问数据的方式和 JDBC 的 ResultSet 是一样的,直接从字节数组里拿数据,比 RowRecord 更高效。 完整的示例代码位置: https://github.com/apache/incubator-iotdb/blob/master/example/session/src/main/java/org/apache/iotdb/SessionExample.java 连接池SessionPool 自从原生接口诞生以来,很多用户就从 JDBC 迁移到原始接口了,我们也扩充了原生接口的能力,增加了 Session 的连接池(东哥倾情奉献)。连接池的接口和 Session 基本一样,但是连接池可以供多线程使用,而且可以屏蔽连接异常等问题。 使用连接池唯一一点需要注意的是,查询得到的结果集使用完需要返还给连接池(调用连接池的 closeResultSet 方法),不然连接会被占用,无法得到新的连接就报超时了。 SessionDataSetWrapper wrapper = null; try{ wrapper=pool.executeQueryStatement("select*fromroot.sg1.d1"); while(wrapper.hasNext()){ System.out.println(wrapper.next()); } } catch (IoTDBConnectionException | StatementExecutionException e) { e.printStackTrace(); }finally{ //remembertoclosedatasetfinally! pool.closeResultSet(wrapper); } 完整的示例代码位置: https://github.com/apache/incubator-iotdb/blob/master/example/session/src/main/java/org/apache/iotdb/SessionPoolExample.java Python 接口 除了 JAVA 的接口,我们还包装了一下 Python 的接口,是在 0.9 版本之后才增加的。位置在 https://github.com/apache/incubator-iotdb/blob/master/client-py 总结 今天主要介绍了 JDBC 接口、JAVA 原生接口 Session 和连接池,里边有一些注意事项,比如使用 SessionPool 做查询时,需要手动关闭结果集(我对这个印象深刻,曾经踩了几天的坑)。

资源下载

更多资源
Mario

Mario

马里奥是站在游戏界顶峰的超人气多面角色。马里奥靠吃蘑菇成长,特征是大鼻子、头戴帽子、身穿背带裤,还留着胡子。与他的双胞胎兄弟路易基一起,长年担任任天堂的招牌角色。

腾讯云软件源

腾讯云软件源

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

Spring

Spring

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

Sublime Text

Sublime Text

Sublime Text具有漂亮的用户界面和强大的功能,例如代码缩略图,Python的插件,代码段等。还可自定义键绑定,菜单和工具栏。Sublime Text 的主要功能包括:拼写检查,书签,完整的 Python API , Goto 功能,即时项目切换,多选择,多窗口等等。Sublime Text 是一个跨平台的编辑器,同时支持Windows、Linux、Mac OS X等操作系统。

用户登录
用户注册