独行侠梦

独行侠梦 查看完整档案

填写现居城市  |  填写毕业院校  |  填写所在公司/组织填写个人主网站
编辑

欢迎关注公众号【侠梦的开发笔记】

github开源调用链工具pinpoint 公司分支改造维护者,实现在 2000 多个微服务监控中落地部署,熟悉内容包括但不限于Jaeger/SkyWalking/淘宝鹰眼等APM组件,
关注 java、微服务架构、消息队列、elasticsearch 等领域。
号内记录开发历程,分享开发心得。长路漫漫,未来可期,望与你砥砺前行。

个人动态

独行侠梦 发布了文章 · 2020-05-05

聊一聊程序猿该如何做副业

首先自我介绍一下,我是一名90后,从事的是java开发的工作,和大多程序猿一样,加班熬过夜,赶过项目工期,撕过产品。

副业的初衷

我知道踏下心来钻研本专业会有收获,但又实在没有太多兴趣。 只好努努力,跟着兴致走,多一个选择,多一份抵抗风险的能力。

不管从事什么行业,除了做好自己本职工作以外,还会有一些空闲的时间来做一些其他事情,与其说是副业,不如说是一个成年人在生活挤压下的心理疏导。就算不那么成功,起码一直在走。

人不成熟的第一个特征就是做事情立马就需要回报,不然无法坚持下去。

有的人一个月放弃,有的人三个月放弃,有的人半年放弃,有的人一年放弃。放弃是一种习惯。

何以解忧,唯有暴富。大家都在用这句话开玩笑,其实也是内心深处的一份憧憬吧。

在大公司,可能管理比较规范,每个人就像螺丝钉一样做好自己那一块就好,但长此以往,可能在短时间内难以建立起自己的一课技能树,小公司负责的事情会很杂,真正投入进去的话,可能还会觉得时间完全不够用,每天都是忙忙碌碌的,但是总结起来其实好像都是在做“体力活“ ,就像机器人一样,那里需要就往哪里冲,各种事情都有可能做,最终可能培养成万能程序大牛。

相信很多人都是,除了勤勤恳恳的工作外,再有一份能够带来不错收入的副业。不至于像创业一样要全情投入没有退路,但又能增加收入提升生活品质,这可能就是搞副业的初衷吧。

搞过那些副业?

可以做那些副业,相信你一定听过很多,这里我只想记录一下,我做过的一些。

1、接私活

你一定听说过,猪八戒、一品威客这些的威客网站吧,这类网站大多需要采用开店铺的方式来入驻,需要押金等等,如果是个人开发者,不太推荐在这上面接项目,一般接私活最好是通过朋友或者熟人介绍比较靠谱。

说一下接过的一个私活,当时客户说是要做一个类似抖音一样的短视频平台,每个人都可以发布视频,会有打赏、点赞、评论等,平台从中抽成。

最开始听到这些需求的时候,并没有感觉畏惧,而是觉得,哇塞,这是一个大单啊!

不过,经过沟通后,发现自己想多了,其实客户要的就是普通的上传视频,播放视频而已,根本没有类似直播等这类需求。梳理出来的功能如下:file

主界面如下:

file

上传视频界面:

file

当时报价报的是8000,包括PC端和移动端,微信支付等,由于客户不是很急,所以也没有特别严格的要求工期,主要是利用下班和周末的时间,前前后后做了一个月,期间也变更了几次需求,不过项目不大,各个方面都还能控制。

接私活,并不是那么容易,建议优先考虑熟人介绍。

2、接单

前面说的接私活,是指的整个项目,这里的接单说的是只接一个项目的部分功能,许多项目会以众包的方式放单,每个人可以接其中的一两个模块,比如一个管理系统的用户管理、部门管理模块。不过这种是协同开发模式,各个开发者之间彼此都不熟悉,也存在一定的风险。

笔者先前经历过一个小程序的开发众包,那个团队已经跑路了,现在还在每天推代办

file

值得一提的是,腾讯的这个TAPD项目协作还是做得不错的,挺好用。

file

3、作业代做

这里的作业指的是两种,一种是毕业设计,另一种是海外留学生的作业。

比较推荐后者,大多还是比较简单的,算法和数据结构的实验题比较多,当然还有一些java、python、matlab等的一些编程题。

为什么推荐做海外留学生作业?

因为作业全是英文的,一方面能够锻炼自己的英文阅读能力,一方面也能锻炼编程,还有报酬。 一举三得。

file

4、平台投稿

如果你喜欢写作和分享,那可以给很多平台投稿,大多都会有一些稿酬。

比较推荐的有三个平台,一个是InfoQ:file

另一个是51CTO博客

file

还有一个是Gitchat,这里就不贴图了,大家可以自行去了解。

4、MIUI主题设计 制作手机主题,设定了价格后,上传到小米主题商店,有人购买就会有分成收入,前一阵看小米主题年报,有设计师做这个年入百万的,也是满励志。上个图(我这个是直接卖给MIUI商店的,所以按一套多少做的支付)

0成本商品推广

以前写过一篇,如何做一个能赚钱的微信机器人,其实就是做的淘宝客推广,可能很多人不知道什么是淘宝客,这里简单解释一下,就是很多淘宝京东上的店铺,把原本用来广告推广的费用作为佣金,让用户去推广,如果是自买,自己获取优惠券的同时,还有一份佣金收入。

如果有开发能力,可以开发一个属于自己的微信返利机器人,如果不想那么麻烦,那可以通过一些平台实现这样的功能,比如芬香。不用投一分钱,自己网购使用也是省钱的。邀请码可以用T9CTHA,这个主要是在本文中门槛比较低并且回报比较大的一种方式了,以下是我的收益截图:

file

以上就是笔者经历过的一些知识变现的经历,当然还有很多很多思路,比如写公众号文章,开设自己的知识星球,总之方法很多,主要是如何管理自己时间,毕竟人的精力是有限的。

根据自己的性格选择适合的副业

说了这么多方法,当然还是需要结合自己的实际情况来选择适合自己的副业,最重要。 比如外向型,特别喜欢说话、沟通,那比较适合做销售,说到销售,不是光光指房产,这里说一个思路,通过喜马拉雅fm的有声化制作平台,通过声音就能变现,还有做自媒体,做视频等等。 内敛型,适合做文职 对于不太喜欢与人打交道的人来说,由于性格方面的原因,这些人相对来说比较文静比较沉稳,对于这种内敛型的人,更适合做一些文职类的工作。当内敛型的人静下心来便可在网上做各种适合的文职工作,例如现在非常火爆的公众号文章,头条,百家号等自媒体,以及可以承接网络上的兼职编辑等工作。

最好的状态是,主业和副业相互促进,两条线呈螺旋状上升。

你的本钱包括资金、时间、智慧、劳力、技术、兴趣和智慧等,只要你能创造价值的本领都可以称之为你的本钱。 希望笔者的这些经历,能给你带来一些启发。

如果你也认同这些观点,点个赞吧

查看原文

赞 0 收藏 0 评论 1

独行侠梦 关注了专栏 · 2020-04-18

CodeSheep的技术分享

分享虚拟化、容器化、API化、微服务化的WEB技术,更多务实、能看懂、可复现的原创文章尽在作者公众号 CodeSheep,欢迎订阅

关注 5182

独行侠梦 关注了专栏 · 2020-04-18

360 技术

360 公司官方技术专栏,打造 360 最专业的技术聚合平台

关注 2391

独行侠梦 发布了文章 · 2020-03-26

Nginx双机主备(Keepalived实现)

前言

首先介绍一下Keepalived,它是一个高性能的服务器高可用或热备解决方案,起初是专为LVS负载均衡软件设计的,Keepalived主要来防止服务器单点故障的发生问题,可以通过其与Nginx的配合实现web服务端的高可用。

Keepalived以VRRP协议为实现基础,VRRP是Virtual Router Redundancy Protocol(虚拟路由冗余协议)的缩写,VRRP协议将两台或多台路由器设备虚拟成一个设备,对外提供虚拟路由器IP(一个或多个)。

VRRP出现的目的就是为了解决静态路由的单点故障问题的,它能保证当个别节点宕机时,整个网络可以不间断地运行。

下面我们介绍一下nginx keepalived高可用方案的部署安装。

环境准备

在两台主机上准备如下压缩文件:

  • keepalived-2.0.20.tar.gz
  • nginx-1.16.1.tar.gz

虚拟IP

真实IP

nginx端口

主从

192.168.124.20

192.168.124.13

80

MASTER

192.168.124.20

192.168.124.14

80

BACKUP

安装nginx

file新建一个用户:

useradd tianyan

确定安装目录,我这里的安装目录是:/home/tianyan/tianyan_soft/nginx.install。

在这个目录下分别新建两个目录用于安装nginx和keepalived,解压两个压缩包。

执行安装命令:

./configure --prefix=/home/tianyan/tianyan_soft/nginx.install \
--sbin-path=/home/tianyan/tianyan_soft/nginx.install/sbin/nginx --conf-path=/home/tianyan/tianyan_soft/nginx.install/conf/nginx.conf \
--error-log-path=/home/tianyan/tianyan_soft/nginx.install/error.log \
--http-log-path=/home/tianyan/tianyan_soft/nginx.install/access.log \
--pid-path=/home/tianyan/tianyan_soft/nginx.install/nginx.pid \
--lock-path=/home/tianyan/tianyan_soft/nginx.install/nginx.lock \
--user=tianyan --group=tianyan \
--with-http_stub_status_module \
--with-http_gzip_static_module \
--with-http_ssl_module \
--with-http_realip_module \
--with-threads  \
--with-pcre \
--http-client-body-temp-path=/home/tianyan/tianyan_soft/nginx.install/client/  \
--http-proxy-temp-path=/home/tianyan/tianyan_soft/nginx.install/proxy/ \
 --http-fastcgi-temp-path=/home/tianyan/tianyan_soft/nginx.install/fcgi/  \
--http-uwsgi-temp-path=/home/tianyan/tianyan_soft/nginx.install/uwsgi  \
--http-scgi-temp-path=/home/tianyan/tianyan_soft/nginx.install/scgi

如果报错,记得安装相关依赖:

yum install gcc gcc-c++
nginx: [emerg] bind() to 0.0.0.0:80 failed (13: Permission denied)。

注:以非root权限启动时,会出现 nginx: [emerg] bind() to 0.0.0.0:80 failed (13: Permission denied) 错误。

原因:Linux只有root用户可以使用1024一下的端口

解决办法:

  • 1.按照root权限启动
  • 2.将 /usr/local/nginx/conf/nginx.conf 文件中的80端口改为1024以上。

安装keepalived

./configure --prefix=/usr/local/keepalived

上述命令执行完毕后继续执行:

make && make install

安装完毕后,目录是这个样子:

file

将配置文件拷贝到系统对应的目录下

mkdir /etc/keepalived
cp /usr/local/keepalived/etc/keepalived/keepalived.conf /etc/keepalived/keepalived.conf
cp /usr/local/keepalived/etc/sysconfig/keepalived /etc/sysconfig/keepalived

编辑 master 节点的 keepalived.conf

vim /etc/keepalived/keepalived.conf

内容参考如下:

! Configuration File for keepalived

global_defs {
    #一个没重复的名字即可
    router_id hyq_slave
   }


#ng是否运行
vrrp_script chk_nginx {
        script "/etc/keepalived/nginx_check.sh"
        interval 2
        weight -20
}

vrrp_instance VI_1 {
    state BACKUP      #  必填,可以是MASTER或BACKUP

    interface ens33
    virtual_router_id 101
    priority 90
    advert_int 1

    # 如果两节点的上联交换机禁用了组播,则采用vrrp单播通告的方式
    # 本机ip
    unicast_src_ip 192.168.124.14
    unicast_peer {
        # 其他机器ip
        192.168.124.13
    }
    # 设置nopreempt防止抢占资源
    nopreempt

    authentication {
        auth_type PASS
        auth_pass 1111
    }

    # 与上方nginx运行状况检测呼应
    track_script {
        chk_nginx
    }
    virtual_ipaddress {
        192.168.124.20
    }
}

编辑 slave 节点的 keepalived.conf

vim /etc/keepalived/keepalived.conf
! Configuration File for keepalived

global_defs {
    #一个没重复的名字即可
    router_id hyq_slave
   }


#ng是否运行
vrrp_script chk_nginx {
        script "/etc/keepalived/nginx_check.sh"
        interval 2
        weight -20
}

vrrp_instance VI_1 {
    state BACKUP      #  必填,可以是MASTER或BACKUP   


    interface ens33
    virtual_router_id 101
    priority 90
    advert_int 1

    # 如果两节点的上联交换机禁用了组播,则采用vrrp单播通告的方式
    # 本机ip
    unicast_src_ip 192.168.124.14
    unicast_peer {
        # 其他机器ip
        192.168.124.13
    }
    # 设置nopreempt防止抢占资源
    nopreempt

    authentication {
        auth_type PASS
        auth_pass 1111
    }

    # 与上方nginx运行状况检测呼应
    track_script {
        chk_nginx
    }
    virtual_ipaddress {
        192.168.124.20
    }
}

编写nginx_check.sh脚本

在/etc/keepalived目录下新建nginx_check.sh脚本

touch nginx_check.sh

编辑其内容为:

#!/bin/sh
A=`ps -C nginx --no-header |wc -l`
if [ $A -eq 0 ]
then
  /usr/sbin/nginx
  sleep 1
  A2=`ps -C nginx --no-header |wc -l`
  if [ $A2 -eq 0 ]
  then
    systemctl stop keepalived
  fi
fi

含义是:如果 nginx 停止运行,尝试启动,但是如果无法启动,则杀死本机的 keepalived 进程, keepalied将会把虚拟 ip 绑定到 BACKUP 机器上。 注意: /usr/sbin/nginx是nginx的启动命令,如果你安装到其他目录,则相应的替换。

Keepalived的日志

Keepalived日志默认位置是在/var/log/messages目录下。我们将其修改一下。

由于系统是centos7,修改位置为:/lib/systemd/system/keepalived.service

原内容:

EnvironmentFile=-/usr/local/keepalived/etc/sysconfig/keepalived
ExecStart=/usr/local/keepalived/sbin/keepalived $KEEPALIVED_OPTIONS

修改为:

file

修改完毕后重新加载service

systemctl daemon-reload

创建命令软连接:

ln -s /usr/local/keepalived/sbin/keepalived /usr/sbin/keepalived

执行:

keepalived -D -f /etc/keepalived/keepalived.conf
-D   将日志输出到message日志,默认日志也在message
-f    是指定配置文件

修改/etc/sysconfig/keepalived

把KEEPALIVED_OPTIONS="-D" 修改为:KEEPALIVED_OPTIONS="-D -d -S 0"

file

在/etc/rsyslog.conf 末尾添加

local0.*                                                /var/log/keepalived.log

file

最后执行命令:

service rsyslog restart

重启keepalived后就可以看到日志在/var/log/keepalived.log下了。

测试验证VIP

当keepalived和nginx都启动后,我们来测试一下。

首先在浏览器里面访问三个地址

我修改了一下nginx的index.html,可以看到当前vip指向是13的master节点:

file

然后,我们手动停止13上的nginx,再次访问http://192.168.124.20。

file

说明安装成功。

其中通过ip address命令可以观察到网卡的变化file

实验到这里,我们就完成了keepalived + nginx 主从配置的安装部署了。

思考:如何开启双主模式

什么是双主模式?

分别介绍一下两种配置

1、Nginx+keepalived 主从配置

file

这种方案就是上文介绍过的,使用一个vip地址,前端使用2台机器,一台做主,一台做备,但同时只有一台机器工作,另一台备份机器在主机器不出现故障的时候,永远处于浪费状态,仅仅用于灾备,平时都是空闲着的。

2、Nginx+keepalived 双主配置

这种方案,使用两个vip地址,前端使用2台机器,互为主备,同时有两台机器工作,当其中一台机器出现故障,两台机器的请求转移到一台机器负担,如下图:

file

【实战】 elasticsearch 写入速度提升的案例分享

用java做一个能赚钱的微信群聊机器人(PC协议)

Mysql百万量级数据高效导入Redis

java线上故障分析+性能调优

查看原文

赞 1 收藏 1 评论 0

独行侠梦 发布了文章 · 2020-02-10

Mysql百万数据量级数据快速导入Redis

前言

随着系统的运行,数据量变得越来越大,单纯的将数据存储在mysql中,已然不能满足查询要求了,此时我们引入Redis作为查询的缓存层,将业务中的热数据保存到Redis,扩展传统关系型数据库的服务能力,用户通过应用直接从Redis中快速获取常用数据,或者在交互式应用中使用Redis保存活跃用户的会话,都可以极大地降低后端关系型数据库的负载,提升用户体验。

传统命令的缺点

使用传统的redis client命令在大数据量的导入场景下存在如下缺陷:

由于redis是单线程模型,虽然避免了多线程下线程切换所耗费的时间,单一顺序的执行命令也很快,但是在大批量数据导入的场景下,发送命令所花费的时间和接收服务器响应结果耗费的时间就会被放大。

假如需要导入100万条数据,那光是命令执行时间,就需要花费100万*(t1 + t2)。

file

除了逐条命令发送,当然redis设计肯定也会考虑这个问题,所以出现了pipelining管道模式。

file

但是pipelining在命令行中是没有的,使得我们又需要编写新的处理代码,来接收批量的响应。但是只有很少很少的客户端代码支持,比如php-redis的扩展就不支持异步。

pipelining管道模式,其实就是减少了TCP连接的交互时间,当一批命令执行完毕后,一次性发送结果。

其实现原理是采用FIFO(先进先出)的队列来保证数据的顺序性。

只有一小部分客户端支持非阻塞I/O,并不是所有的客户端都能够以一种有效的方式解析应答,以最大化吞吐量。

由于这些原因,将庞大数据导入到Redis的首选方法是生成一个包含Redis协议数据格式,批量的发送过去。

数据导入Redis热身

采用nc命令导入数据

nc是netcat的简写,nc的作用有:

(1)实现任意TCP/UDP端口的侦听,增加-l参数后,nc可以作为server以TCP或UDP方式侦听指定端口

(2)端口的扫描,nc可以作为client发起TCP或UDP连接

(3)机器之间传输文件

(4)机器之间网络测速

file

file

采用pipe模式导入数据

然而,使用nc监听并不是一个非常可靠的方式来执行大规模的数据导入,因为netcat并不真正知道何时传输了所有数据,也无法检查错误。在2.6或更高版本的Redis中,Redis -cli脚本支持一种称为pipe管道模式的新模式,这种模式是为了执行大规模插入而设计的。
使用管道模式的命令运行如下:

file

由上图,可以看到pipe命令的返回结果,txt文件中有多少行命令,返回的replies数就是多少,
errors表示其中执行错误的命令条数。

redis协议学习

协议的格式为:

*<参数数量>  \r\n
$<参数 1 的字节数量>  \r\n
<参数 1 的数据> \r\n
...
$<参数 N 的字节数量> \r\n
<参数 N 的数据> \r\n

比如:
插入一条hash类型的数据。

HSET  id  book1  book_description1

根据Redis协议,总共有4个部分,所以开头为*4,其余内容解释如下:

内容长度协议命令
HSET4$4
id2$2
book15$5
book_description117$17

注意一下:HSET命令本身也作为协议的其中一个参数来发送。

构造出来的协议数据结构:

*4\r\n$4\r\nHSET\r\n$2\r\nid\r\n$5\r\nbook1\r\n$17\r\nbook_description1\r\n

格式化一下:

*4\r\n
$4\r\n
HSET\r\n
$2\r\n
idvvvv\r\n
$5\r\n
book1\r\n
$17\r\n
book_description1\r\n

RESP协议 bulk

Redis客户机使用一种称为RESP (Redis序列化协议)的协议与Redis服务器通信。

redis-cli pipe模式需要和nc命令一样快,并且解决了nc命令不知道何时命令结束的问题。

在发送数据的同时,它同样会去读取响应,尝试去解析。

一旦输入流中没有读取到更多的数据之后,它就会发送一个特殊的20比特的echo命令,标识最后一个命令已经发送完毕
如果在响应结果中匹配到这个相同数据后,说明本次批量发送是成功的。

使用这个技巧,我们不需要解析发送给服务器的协议来了解我们发送了多少命令,只需要解析应答即可。

在解析应答时,redis会对解析的应答进行一个计数,在最后能够告诉用户大量插入会话向服务器传输的命令的数量。也就是上面我们使用pipe模式实际操作的响应结果。

将输入数据源换成mysql

上面的例子中,我们以一个txt文本为输入数据源,使用了pipe模式导入数据。

基于上述协议的学习和理解,我们只需要将mysql中的数据按照既定的协议通过pipe模式导入Redis即可。

实际案例--从Mysql导入百万级数据到Redis

首先造数据

由于环境限制,所以这里没有用真实数据来实现导入,那么我们就先使用一个存储过程来造一百万条数据把。使用存储过程如下:

DELIMITER $$
USE `cb_mon`$$

DROP PROCEDURE IF EXISTS `test_insert`$$
CREATE DEFINER=`root`@`%` PROCEDURE `test_insert`()
BEGIN
    
        DECLARE i INT DEFAULT 1;
        WHILE i<= 1000000
            DO
            INSERT INTO t_book(id,number,NAME,descrition)
            VALUES (i, CONCAT("00000",i) , CONCAT('book',i)
            , CONCAT('book_description',i));    
            SET i=i+1;
        END WHILE ;
        COMMIT;
    END$$

DELIMITER ;

调用存储过程:

 CALL test_insert();

查看表数据:

按协议构造查询语句

按照上述redis协议,我们使用如下sql来构造协议数据

SELECT
  CONCAT(
    "*4\r\n",
    "$",
    LENGTH(redis_cmd),
    "\r\n",
    redis_cmd,
    "\r\n",
    "$",
    LENGTH(redis_key),
    "\r\n",
    redis_key,
    "\r\n",
    "$",
    LENGTH(hkey),
    "\r\n",
    hkey,
    "\r\n",
    "$",
    LENGTH(hval),
    "\r\n",
    hval,
    "\r"
  )
FROM
  (SELECT
    "HSET" AS redis_cmd,
    id AS redis_key,
    NAME AS hkey,
    descrition AS hval
  FROM
    cb_mon.t_book
  ) AS t limit 1000000 

并将内容保存至redis.sql 文件中。

编写脚本使用pipe模式导入redis

编写shell脚本。由于我在主机上是通过docker安装的redis和mysql,以下脚本供参考:

file

#!/bin/bash
starttime=`date +'%Y-%m-%d %H:%M:%S'`

docker exec -i 899fe01d4dbc mysql --default-character-set=utf8   
--skip-column-names --raw < ./redis.sql
| docker exec -i 4c90ef506acd redis-cli --pipe

endtime=`date +'%Y-%m-%d %H:%M:%S'`
start_seconds=$(date --date="$starttime" +%s);
end_seconds=$(date --date="$endtime" +%s);

echo "脚本执行耗时: "$((end_seconds-start_seconds))"s"

执行截图:

file

可以看到百万级的数据导入redis,只花费了7秒,效率非常高。

注意事项

如果mysql表特别大,可以考虑分批导入,或者将表拆分,否则在导入过程中可能会发生

lost connection to mysql server during query

由于max_allowed_packed和超时时间限制,查询数据的过程中,可能会造成连接断开,所以在数据表的数据量特别大的时候,需要分页或者将表拆分导入。

总结

本篇文章主要探讨了,Mysql百万级数据量级下,如何高效的迁移到Redis中去,逐步实现目标的过程中,总结了如下几点

  1. redis单线程执行命令,避免了线程切换所消耗的时间,但是在超大数据量级下,其发送、响应接收的时延不可忽视。
  2. 网络nc命令的应用场景,及在数据导入时存在的缺点。
  3. redis RESP协议的理解和应用。
  4. 百万量级Mysql数据的Redis快速导入案例。
hello,我是【侠梦的开发笔记】的号主,为了方便大家学习讨论,我创建了一个java疑难攻坚互助大家庭,和其他传统的学习交流不同。本群主要致力于解决项目中的疑难问题,在遇到项目难以解决的
问题时,都可以在这个大家庭里寻求帮助。
公众号回复【问题的答案】进入:java中Integer包装类的基本数据类型是?
如果你也经历过遇到项目难题,无从下手,
他人有可能可以给你提供一些思路和看法,一百个人就有一百种思路,
同样,如果你也乐于帮助别人,那解决别人遇到的问题,也同样对你是一种锻炼。
查看原文

赞 3 收藏 2 评论 0

独行侠梦 发布了文章 · 2020-02-06

多线程之CountDownLatch的用法及原理笔记

前言-CountDownLatch是什么?

CountDownLatch是具有synchronized机制的一个工具,目的是让一个或者多个线程等待,直到其他线程的一系列操作完成。

CountDownLatch初始化的时候,需要提供一个整形数字,数字代表着线程需要调用countDown()方法的次数,当计数为0时,线程才会继续执行await()方法后的其他内容。
CountDownLatch(int count);

对象中的方法

getCount:
返回当前的计数count值,
public void countDown()
调用此方法后,会减少计数count的值。
递减后如果为0,则会释放所有等待的线程
public void await()
           throws InterruptedException
调用CountDownLatch对象的await方法后。
会让当前线程阻塞,直到计数count递减至0。

如果当前线程数大于0,则当前线程在线程调度中将变得不可用,并处于休眠状态,直到发生以下两种情况之一:

1、调用countDown()方法,将计数count递减至0。

2、当前线程被其他线程打断。

public boolean await(long timeout,
            TimeUnit unit)
              throws InterruptedException

同时await还提供一个带参数和返回值的方法。

如果计数count正常递减,返回0后,await方法会返回true并继续执行后续逻辑。

或是,尚未递减到0,而到达了指定的时间间隔后,方法返回false。

如果时间小于等于0,则此方法不执行等待。

实际案例

join阻塞等待线程完成

首先建立3个线程。

public class Worker1 implements Runnable {
    @Override
    public void run() {
        System.out.println("-线程1启动");
        try {
            Thread.sleep(13_000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("线程1完成--我休眠13秒\r\n");
    }
}

public class Worker2 implements Runnable {
    @Override
    public void run() {
        System.out.println("-线程2启动");
        try {
            Thread.sleep(3_000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("线程2完成--我休眠3秒\r\n");
    }
}

public class Worker3 implements Runnable {
    @Override
    public void run() {
        System.out.println("-线程3启动");
        try {
            Thread.sleep(3_000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }


        try {
            Thread.sleep(3_000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("线程3完成--我休眠6秒\r\n");
        System.out.println();
    }
}


public class Main {
    public static void main(String[] args) throws InterruptedException {
        Worker1 worker1 = new Worker1();
        Worker2 worker2 = new Worker2();
        Worker3 worker3 = new Worker3();

        Thread thread1 = new Thread(worker1,"线程1");
        Thread thread2 = new Thread(worker2,"线程2");
        Thread thread3 = new Thread(worker3,"线程3");

        thread1.start();
        thread2.start();
        thread3.start();

        thread1.join();
        thread2.join();
        thread3.join();
        System.out.println("主线程结束....");

    }
}

打印结果如下:

-线程3启动
-线程2启动
-线程1启动
线程2完成--我休眠3秒
线程3完成--我休眠6秒

线程1完成--我休眠13秒

主线程结束....
Process finished with exit code 0

可以看出三个线程是并行执行的。启动顺序,并不和执行完毕的顺序一致,但可以明确的是,主线程为一直阻塞,直到三个线程执行完毕。

CountDownLatch用法

阿里巴巴的数据库连接池Druid中也用了countDownLatch来保证初始化。
file

// 开启创建连接的线程,如果线程池createScheduler为null,
//则开启单个创建连接的线程
createAndStartCreatorThread();  

 // 开启销毁过期连接的线程
createAndStartDestroyThread(); 

自己编写一个例子:
这里模拟一种情况:
主线程 依赖 线程A初始化三个数据,才能继续加载后续逻辑。

file

public class CountDownArticle {
    /**
     * 模拟 主线程 依赖 线程A初始化一个数据,才能继续加载后续逻辑
     */
    public static void main(String[] args) throws InterruptedException {
        AtomicReference<String> key = new AtomicReference<>("");
        CountDownLatch countDownLatch = new CountDownLatch(3);
            Thread t = new Thread(() -> {
            try {

                //休眠5秒,模拟数据的初始化
                TimeUnit.SECONDS.sleep(5);

                key.set("核心秘钥123456");
                System.out.println("数据1初始化完毕");

                //释放---此处可以在任何位置调用,很灵活
                countDownLatch.countDown();

                System.out.println("数据2初始化完毕");
                countDownLatch.countDown();

                System.out.println("数据3初始化完毕");
                countDownLatch.countDown();

            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        });
        t.start();

        //等待数据初始化,阻塞
        countDownLatch.await();
        System.out.println("key:" + key.get());
    }
}

打印内容如下:

数据1初始化完毕
数据2初始化完毕
数据3初始化完毕
key:核心秘钥123456

CountDownLatch和Join用法的区别?

在使用join()中,多个线程只有在执行完毕之后欧才能被解除阻塞,而在CountDownLatch中,线程可以在任何时候任何位置调用countdown方法减少计数,通过这种方式,我们可以更好地控制线程的解除阻塞,而不是仅仅依赖于连接线程的完成。

join()方法的执行逻辑如下图所示:

file

原理

从源码可以看出,CountDownLatch是依赖于AbstractQueuedSynchronizer来实现这一系列逻辑的。

队列同步器AbstractQueuedSynchronizer
是一个用来构建锁和同步器的框架,它在内部定义了一个被标识为volatile的名为state的变量,用来表示同步状态。

多个线程之间可以通过AQS来独占式或共享式的抢占资源。

并且它通过内置的FIFO队列来完成线程的排队工作。

file

CountDownLatch中的Sync会优先尝试修改state的值,来获取同步状态。例如,如果某个线程成功的将state的值从0修改为1,表示成功的获取了同步状态。 这个修改的过程是通过CAS完成的,所以可以保证线程安全。

反之,如果修改state失败,则会将当前线程加入到AQS的队列中,并阻塞线程。

总结

CountDownLatch(int N) 中的计数器,可以让我们支持最多等待N个线程的操作完成,或是一个线程操作N次。

如果仅仅只需要等待线程的执行完毕,那么join可能就能满足。但是如果需要灵活的控制线程,使用CountDownLatch。

注意事项

countDownLatch.countDown();

这一句话尽量写在finally中,或是保证此行代码前的逻辑正常运行,因为在一些情况下,出现异常会导致无法减一,然后出现死锁。

CountDownLatch 是一次性使用的,当计数值在构造函数中初始化后,就不能再对其设置任何值,当 CountDownLatch 使用完毕,也不能再次被使用。

写在最后

为了方便大家学习讨论,我创建了一个java疑难攻坚互助大家庭,和其他传统的学习交流不同。本群主要致力于解决项目中的疑难问题,在遇到项目难以解决的
问题时,都可以在这个大家庭里寻求帮助。

公众号回复【问题的答案】进入:java中Integer包装类的基本数据类型是?
如果你也经历过遇到项目难题,无从下手,
他人有可能可以给你提供一些思路和看法,一百个人就有一百种思路,
同样,如果你也乐于帮助别人,那解决别人遇到的问题,也同样对你是一种锻炼。

欢迎来公众号【侠梦的开发笔记】 ,回复干货,领取精选学习视频一份
查看原文

赞 0 收藏 0 评论 0

独行侠梦 发布了文章 · 2020-01-21

同样是后端程序员,你掌握如何用Grafna做出漂亮的可视化界面了吗?

前言

Grafana 有着非常漂亮的图表和布局展示,功能齐全的度量仪表盘dashboard和图形编辑器。此篇文章主要来记录学习 如何使用mysql打造属于我们的可视化监控仪表盘。

安装grafna

采用如下命令直接安装grafna。或是下载.tar.gz包自行安装。

sudo apt-get install -y apt-transport-https
sudo apt-get install -y software-properties-common wget
wget -q -O - https://packages.grafana.com/gpg.key | sudo apt-key add -
sudo add-apt-repository "deb https://packages.grafana.com/oss/deb stable main"
sudo apt-get update
sudo apt-get install grafana

在一个Dashboard中一个最基本的可视化单元为一个Panel(面板)。

每一个Panel可以配置自己查询的数据源以及数据查询方式。
当然这里我们使用Mysql作为数据源,其查询方式就是Sql语句。

扩展一下思路:
由于每个Panel是完全独立的,因此在一个Dashboard中,往往可能会包含来自多个Data Source的数据。也就是说A panel你可以从mysql中查询数据,B panel你可以从es中查询,很灵活的自由组合。

grafna启动相关命令

查看grafna状态
sudo systemctl status grafana-server

重启grafna
service grafana-server restart

配置grafna等操作,可以参看官方文档:

https://grafana.com/docs/grafana/latest/installation/configuration/

运行一个官网的案例

Grafana还专门为Dashboard提供分享服务,你也可以把自己做得漂亮的界面上传上去供大家参考交流,传送门是:

https://grafana.com/dashboards

在这里,我使用的是Id为7991的监控面板。

file

添加mysql数据源

首先,从左侧的菜单栏配置项中选中数据源。

file

grafna支持的数据源比较多,这里我们手动搜索一下mysql。

file

选中之后,填写完具体的数据库地址和密码后,保存即可。

导入面板

解决报错

file

导入面板后后提示Templating init failed Error 1146: Table 'my2.status' doesn't exist错误,这是因为我们还有一个工作没做完,监控mysql时需要在mysql server运行的机器上安装my2.status表,根据官网面板的说明,找到github仓库地址:

https://github.com/meob/my2Collector

在数据库中执行my2.sql 就能成果导入了,效果如下:

file

从说明我们可以看到,
My2Collector (my2)是一个简单的、自我包含的MySQL统计信息收集器,my2每10分钟自动执行一次存储程序来收集Mysql的一些性能数据。

版本支持

my2可以连接到任何版本的MySQL,MariaDB,Percona或其他fork,但是...对于旧的MySQL,发布的许多统计信息均不可用。

my2使用的计划作业自MySQL 5.1(2008)起可用。

PROCESSLIST表从5.1.7开始可用。

而GLOBAL_STATUS从5.1.12开始可用。

PERFORMANCE_SCHEMA在5.5版本中引入,在5.6版本中得到了极大的增强。

不同的MySQL版本之间有很多细微的差别:My2意识到并做了兼容,尝试收集所有可用信息,对于MySQL 8.0,提供了一个不同的脚本,my2在启用了性能模式的MySQL 5.7,MySQL 8.0和MariaDB 10.x中发挥了最大作用。

实战,基于Mysql业务表来创建监控

最简单的列表展示

在 面板中选择table类型的 panel。
file

编写查询的sql语句,可以使用具体的别名。
file

多个折线图

file

特别注意时间序列:

返回列名中需要有time或time_sec的列,作为unix时间戳或任何sql原生日期。

特别注意,这里的时间转换函数:用法

- $__time(column) -> UNIX_TIMESTAMP(column) as time_sec
- $__timeEpoch(column) -> UNIX_TIMESTAMP(column) as time_sec
- $__timeFilter(column) -> column BETWEEN FROM_UNIXTIME(1492750877) AND FROM_UNIXTIME(1492750877)
- $__unixEpochFilter(column) ->  time_unix_epoch > 1492750877 AND time_unix_epoch < 1492750877
- $__unixEpochNanoFilter(column) ->  column >= 1494410783152415214 AND column <= 1494497183142514872

- $__timeGroup(column,'5m'[, fillvalue]) -> cast(cast(UNIX_TIMESTAMP(column)/(300) as signed)*300 as signed)
     by setting fillvalue grafana will fill in missing values according to the interval
     fillvalue can be either a literal value, NULL or previous; previous will fill in the previous seen value or NULL if none has been seen yet
- $__timeGroupAlias(column,'5m') -> cast(cast(UNIX_TIMESTAMP(column)/(300) as signed)*300 as signed) AS "time"
- $__unixEpochGroup(column,'5m') -> column DIV 300 * 300
- $__unixEpochGroupAlias(column,'5m') -> column DIV 300 * 300 AS "time"

由上面的规则可知,如果数据里面的数据类型是bigint。那么时间筛选就需要使用:

$__unixEpochFilter(data_time/1000)

柱状图

只需要在折线图的配置基础上,将model改为Bars即可展示柱状图。

file

Graph里面的选项有:General(常规选择)、Metrics(指标)、Axes(坐标轴)、Legend(图例)、 Display(显示样式)、Alert(告警)、Time range(时间范围)

file

file

为了方便大家学习讨论,我创建了一个java疑难攻坚互助大家庭,和其他传统的学习交流不同。本群主要致力于解决项目中的疑难问题,在遇到项目难以解决的
问题时,都可以在这个大家庭里寻求帮助。

公众关号注:侠梦的开发笔记后回复【问题的答案】进入:java中Integer包装类的基本数据类型是?
如果你也经历过遇到项目难题,无从下手,
他人有可能可以给你提供一些思路和看法,一百个人就有一百种思路,
同样,如果你也乐于帮助别人,那解决别人遇到的问题,也同样对你是一种锻炼。

欢迎来公众号【侠梦的开发笔记】 ,回复干货,领取精选学习视频一份
查看原文

赞 0 收藏 0 评论 0

独行侠梦 发布了文章 · 2020-01-21

Mysql Shell免密登录的思考及实际应用案例

前言

数据库图形化工具的出现,给我们操作数据库带来了许多便利,但是过度的依赖工具,始终无法明白其内部 的一些原理,离开了工具,可能寸步难行,所以尽量使用原生的命令行来操作数据库,可以增加熟练度,提高我们知识的深度。

最常见的明文登录方式

以下mysql命令参数,相信大家已经是耳熟能详了:

-h参数指定mysql主机。

-u指定mysql用户。

-P(大写)指定端口。

-p(小写)指定密码。

这里衍生出两个问题:

1)每次使用都需要指定一长串的参数和密码。

2)密码直接在命令行暴露出来,运行时,在一个短暂的时间间隔内,是可以直接通过ps命令查看到你数据库的密码的,所以十分不安全。

当然你可以不用指定密码参数,而是选择手动输入。

file

明文配置文件的方式登录

如果你不想在控制台,以交互的方式输入密码,那还有一种方式,将明文密码写入到文件中。

file

当然也可以试试验证读取配置情况:

mysql --defaults-file=./A.cnf --print-defaults

file

这里打印的意思是:mysql将会使用如下的参数进行登录连接。

小结

总结来说:指定明文密码的两种方式是:

1)通过指定--password (-p) 交互式的输入密码。

2)将密码放在一个配置 文件中。

当我们使用一些数据库的shell脚本时,当然不能这样交互式的输入密码,并且我们也不想把密码暴露到配置文件中,那该怎么做呢?

学习mysql命令的参数配置项

不知道如何实现,我们首先来熟悉熟悉mysql命令提供的参数。
默认情况下,mysql会按如下顺序读取配置:

1) /etc/my.cnf

2)/etc/mysql/my.cnf

3)~/.my.cnf

参数:

  • --no-defaults ,不读取配置文件中的任何内容。但是mylogin.cnf除外。
  • --login-path,指定在mysql_config_editor中设置选项组名称。
  • --print-defaults,打印从参数配置文件中获得的所有选项。密码值会被屏蔽。

了解了上述的基本参数作用后,我们来学习,如何保护我们的密码。

使用mysql_config_editor存储登录信息

mysql_config_editor是一个存储mysql登录信息的工具,
它会将你的身份认证信息存储到一个名为 .mylogin.cnf 的登录路径文件中。

由于该文件是隐藏文件,你可以用如下命令查看:

ls -l .mylogin.cnf 

文件的内容是进行了混淆加密的,明文方式它存储的文件内容大概是:

[pathA]
user = aaa
password = aaapwd
host = ip1

[pathB]
user = root
password = pwd2
host = ip2

这里,可以有多组配置,每一个用括号包裹的块,我们称它为选项组,每个选项组内容包括:主机、用户、密码、端口、套接字等信息。

当调用Mysql客户端命令连接到服务器时,客户端将mylogin.cnf与其他指定的参数配置文件一起使用。这里的优先级高于其他文件,但低于在客户端命令行上显式指定的配置。

file

当然啦,我们也可以显示的指定用那个选项组,就像是使用A计划还是B计划一样。默认mysql会执行[client] 和 [mysql] 选项组配置。

将密码放入受保护的文件

要查看mysql_config_editor写入.mylogin.cnf文件的内容,我们可以使用如下命令:

mysql_config_editor print --all

mysql_config_editor print --login-path=pathA

登录路径文件必须对当前用户是可读和可写的,而对其他用户是不可访问的。否则,mysql_config_editor将忽略它,客户机程序也不会使用它,一个完整的设置命令入下:

mysql_config_editor set --login-path=pathA  \
--host=localhost --user=root --password --port=3306 \
--socket=~/pathA_mysql.sock

操作帮助说明可以通过执行如下命令查看:

mysql_config_editor set --help

其中--socket指定mysql以socket方式运行的sock文件位置。
--password 指定需要密码,这里会在回车后让你输入密文。
它默认在数据存储到内容中,
并且,我们使用print打印的时候,它同样也不会显示明文,是不是安全多了呢。

file

登录命令变成:

mysql --login-path=pathA

实际案例应用

完成上述配置后,我们可以愉快的使用密文登录mysql啦。这里提供一个实际的案例。

要求:编写crontab定时任务,定时的清理mysql A表一天前的数据,每天早上6点执行,需要提供日志记录显示执行状态。

小试牛刀

上面我们已经配置好了免密登录,下面来试试执行一条sql语句。

mysql --login-path=pathA -e 'select 1'

file

获取今天凌晨的毫秒值:

select UNIX_TIMESTAMP(CAST(SYSDATE()AS DATE)) * 1000

没有任何问题,按照要求,我们编写带记录日志的shell脚本,参考如下:

#!/bin/sh
# adirname - return 绝对路径
adirname() { odir=`pwd`; cd `dirname $1`; pwd; cd "${odir}"; }
MYNAM=`basename "$0"`
MYDIR=`adirname "$0"`
MYHOME="/home/appuser"
MYLOG_PATH="${MYHOME}/logs"
MYLOG="${MYLOG_PATH}/${MYNAM}_`date +%F`.log"

if [ ! -f "$MYLOG" ]; then
    touch "$MYLOG"
fi

# 记录日志
function loginfo(){
    echo "$1"
    echo "$(date -d today +"%Y%m%d %H:%M:%S") - $1"  >> ${MYLOG} 2>> ${MYLOG}
}

mysql --login-path=pathA -e 'delete from A where time < UNIX_TIMESTAMP(CAST(SYSDATE()AS DATE)) * 1000'

loginfo "handle delete table successfully,$?"

加入crontab定时任务

#每天6点执行
0 6 * * * /bin/sh /home/root/del_tabl_task.sh

总结

本文对比介绍了 明文密码登录mysql的两种方式和密文登录。希望你了解如下知识点:
1、mysql_config_editor工具的用法及作用?

2、mysql shell脚本的简单使用?

3、crontab定时任务脚本的配置使用。

4、如何在shell中记录输出日志到文件。

当然这些知识可能都只是冰山一角,希望对你有所启发。

为了方便大家学习讨论,我创建了一个java疑难攻坚互助大家庭,和其他传统的学习交流不同。本群主要致力于解决项目中的疑难问题,在遇到项目难以解决的
问题时,都可以在这个大家庭里寻求帮助。

公众关号注侠梦的开发笔记后回复【问题的答案】进入:java中Integer包装类的基本数据类型是?
如果你也经历过遇到项目难题,无从下手,
他人有可能可以给你提供一些思路和看法,一百个人就有一百种思路,
同样,如果你也乐于帮助别人,那解决别人遇到的问题,也同样对你是一种锻炼。

欢迎来公众号【侠梦的开发笔记】 ,回复干货,领取精选学习视频一份
查看原文

赞 0 收藏 0 评论 0

独行侠梦 发布了文章 · 2020-01-17

侠说java8-LocalDateTime等时间使用手册(全),先mark后看

前言

java8的时间日期api给我们提供了极大的便利。如何更好的熟悉使用时间api也是学习java8的一个很重要的知识点,下面我们一起来学习学习。

本篇文章代码比较多,可以作为工具,需要使用时,再来查阅。

目录

1.普通Date时间如何转为LocalDateTime?

file

以上是从普通的Date对象转换成java8时间的操作步骤,
需要特别注意下时区的问题。

    // 设置时区
//      ZoneId defaultZoneId = ZoneId.systemDefault();
        ZoneId defaultZoneId = ZoneId.of("Asia/Shanghai");
        System.out.println("set  TimeZone : " + defaultZoneId);

        //以下每个注释为输出的结果

        //date : Fri Jan 17 16:52:59 CST 2020
        Date date = new Date();
        System.out.println("date : " + date);

        //1. instant : 2020-01-17T08:52:59.235Z
        Instant instant = date.toInstant();
        System.out.println("instant : " + instant); //Zone : 默认是UTC+0时区

        //2. localDate : 2020-01-17
        LocalDate localDate = instant.atZone(defaultZoneId).toLocalDate();
        System.out.println("localDate : " + localDate);

        //3. LocalDateTime: 2020-01-17T16:52:59.235
        LocalDateTime localDateTime = instant.atZone(defaultZoneId).toLocalDateTime();
        System.out.println("localDateTime : " + localDateTime);

        //4. ZonedDateTime: 2020-01-17T16:52:59.235+08:00[Asia/Shanghai]
        ZonedDateTime zonedDateTime = instant.atZone(defaultZoneId);
        System.out.println("zonedDateTime : " + zonedDateTime);

2. java8时间如何转换为普通Date?

转换的过程和上面图例刚好反过来,代码如下:

  //同样,我们设置时区为上海
        ZoneId defaultZoneId = ZoneId.of("Asia/Shanghai");
        System.out.println(" Default TimeZone : " + defaultZoneId);

        LocalDate localDate = LocalDate.of(2020, 01, 17);
        Date date = Date.from(localDate.atStartOfDay(defaultZoneId).toInstant());
        System.out.println("\n1. LocalDate -> Date");
        System.out.println("localDate : " + localDate);
        System.out.println("date : " + date);

        LocalDateTime localDateTime = LocalDateTime.of(2020,01,17,17,46,31);
        Date date2 = Date.from(localDateTime.atZone(defaultZoneId).toInstant());
        System.out.println("\n2. LocalDateTime -> Date");
        System.out.println("localDateTime : " + localDateTime);
        System.out.println("date2 : " + date2);

        ZonedDateTime zonedDateTime = localDateTime.atZone(defaultZoneId);
        Date date3 = Date.from(zonedDateTime.toInstant());
        System.out.println("\n3. ZonedDateTime -> Date");
        System.out.println("zonedDateTime : " + zonedDateTime);
        System.out.println("date3 : " + date3);
3.如何比较时间?
  • 无论是Date,还是java8中的localDate和localDateTime都可以通过

compareTo方法来比较时间。

SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
Date date1 = sdf.parse("2020-01-17");
Date date2 = sdf.parse("2020-01-16");
System.out.println("前者如果较大则为正,相等为0,小于为负 : " + date1.compareTo(date2));

LocalDate 和LocalDateTime提供了3个方法,isAfter(),isBefore(),isEqual()。

而在java8之前,我们是使用 after(), before(),equals。方法名有所区别,但是功能大同小异。

4.java8中的瞬点Instant

Instant代表一个瞬时的时间点值对象。

它从1970-01-01T00:00:00Z点毫秒计算的,是不可变的,并且是线程安全的。

5.获取当前时间戳方式?

方式一:new Timestamp(System.currentTimeMillis());

方式二:new Date().getTime();

方式三:Instant。

方式四:Calendar.getInstance()

方式五:LocalDateTime.now()

方式六:LocalDate.now()

Timestamp timestamp = new Timestamp(System.currentTimeMillis());

//2020-01-17 17:04:53.346
System.out.println(timestamp);

//return 1579251953703
System.out.println(timestamp.getTime());

// 时间戳转换为 instant 2020-01-17T09:05:53.703Z
Instant instant = timestamp.toInstant();
System.out.println(instant);

//return 1579251953703
System.out.println(instant.toEpochMilli());

//  instant to timestamp 1579251893346
Timestamp tsFromInstant = Timestamp.from(instant);
System.out.println(tsFromInstant.getTime());

6.时间转String

时间和字符串的转换,相信大家都知道一般使用SimpleDateFormat来实现,但是这个api使用不当可能会有线程安全问题,这里推荐使用如下的方式来做转换,保证thread-safe。

   //使用当前时间测试
LocalDateTime now = LocalDateTime.now();

System.out.println("Before : " + now);

DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");

String formatDateTime = now.format(formatter);

System.out.println("After : " + formatDateTime);

7.String转时间

String now = "2020-01-17 17:30";

DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm");

LocalDateTime formatDateTime = LocalDateTime.parse(now, formatter);

System.out.println("Before : " + now);

System.out.println("After : " + formatDateTime);

System.out.println("After : " + formatDateTime.format(formatter));

8.Instant 转LocalDateTime

Instant instant = Instant.now();
System.out.println("Instant : " + instant);

// 添加时区为上海
LocalDateTime ldt = LocalDateTime.ofInstant(instant, ZoneId.of("Asia/Shanghai"));
System.out.println("LocalDateTime : " + ldt);

9.LocalDateTime转Instant

LocalDateTime dateTime = LocalDateTime.of(2020, 01, 17, 6, 17, 10);

//LocalDateTime : 2020-01-17T06:17:10
System.out.println("LocalDateTime : " + dateTime);

// +hh:mm 如果设置时区为东八区,则要比原来时间晚8小时
//Instant : 2020-01-16T22:17:10Z
Instant instant = dateTime.toInstant(ZoneOffset.of("+08:00"));
System.out.println("Instant : " + instant);
10.java8为什么出现localDate和localDateTime?

在java8以前,时间是通过Date类等来表示,其中包含了一些比较差的API设计。例如,年份java.util.Date从1900开始,月份从1开始,天从0开始,这很不直观。
第三方日期和时间库对此进行了一些弥补的流行,例如Joda-Time。

为了解决这些问题并在JDK内核中提供更好的支持,针对Java SE 8设计了一个新的并且没有这些问题的日期和时间API。

Java SE 8附带新的日期和时间API,java.time该API 为开发人员提供了大大改善的安全性和功能。新的API很好地建模了领域,并提供了用于对各种开发人员用例进行建模的大量类。

欢迎来公众号【侠梦的开发笔记】 ,回复干货,领取精选学习视频一份
查看原文

赞 0 收藏 0 评论 0

独行侠梦 发布了文章 · 2020-01-16

pinpoint实现告警推送至钉钉和微信群

前言

在前面的文章中,我们学习了如何通过java实现将消息发送到钉钉、和将消息发送到微信群聊。

基于上述基础,我们今天来接入pinpoint的告警,发送到钉钉群。

实操前准备

开始之前,推荐阅读一下,官方的告警说明文档。

阅读官方文档后,我们实际的来操作一遍。

按照官方说明,首先需要创建对应的用户组和用户,但是我们这里打开有异常:

file

通过后台,pinpoint-web的日志可以发现,这是由于没有配置mysql表的原因。

file

步骤1-配置mysql表

首先,我们创建一个名为pinpoint的数据库。

file

然后,导入官方的脚本,配置好需要的表。

file

导入的所有表显示如下:

file

导入15张数据表后,我们需要修改pinpoint-web的jdbc.properties配置文件。

配置好正确的数据库和账号密码后,重启pinpoint-web。

步骤2-界面配置用户组和用户

首先配置一个用户组,然后在这个组下面新建一个用户。

用户这里输入id、用户名、手机号、邮箱等信息添加即可。
file

步骤3-添加应用的告警规则

配置完用户后,接着我们配置对应应用的告警规则

file

这里我针对testboot这个服务,配置了一个慢 请求告警,当慢请求大于5个时,就会触发。

这里的type选项指定告警的方式,可以是短信、邮件,或者两者。
file

然后包含具体的告警指标,这里我单纯选择的是Slow count,慢请求个数。

file

具体每个指标的含义,考虑后续再出一篇文章来作详细解释。

这篇主要以实现告警功能为目的,就不再深入了。

至此,告警规则就配置好了。

步骤4-改造源码思路分析

冷静分析

pinpoint 采用Spring Batch框架来运行job,其中
一个Job有多个Step,每个step又包含一系列规定动作(read,process,write)。

界面上所有的可配置指标都是通过Checker计算出来的,我们的Slow Count的也不例外。

file

AlarmMessageSender是我们需要关注的告警接口,它有一个空的实现类,EmptyMessageSender。

本来打算直接在这个类里面写代码的,但转念一想,还是算了,因为他没有被spring管理,是直接new出来的,我们后面可能需要依赖spring的一些bean,避免用着不方便,所以还是自己写一个类来实现把。

file

默认情况下,我们都只是使用不带任何参数的@Autowired。
设置required参数为false,表示忽略当前要注入的bean,如果有直接注入,没有跳过,不会报错。

步骤5-进行源码改造

新建一个类,实现告警接口,源码我上传到了github,需要的小伙伴,公众号回复钉钉,即可获取。

file

写完代码后,我们上传编译后的class文件到WEB-INF/classes/com/navercorp/pinpoint/web/alarm目录下:

file

由于我使用了一个内部类,所以一共有两个class文件。

改一下文件权限

chmod 777 ./DingTalkAndWechatMessageSender.class

chmod 777 ./DingTalkAndWechatMessageSender$DingResponse.class

在/WEB-INF/classes/pinpoint-web.properties配置中新增
web.ding.talk.url属性配置,这个属性就是钉钉的webhok地址。

当然你也可以写具体的微服务地址,来进行更多的逻辑处理,这里为了让大家开箱即用,我将消息直接推送给钉钉。

有了推送的url后,我们在applicationContext-web.xml中添加一个bean配置。

    <bean id="dingTalkAndWechatMessageSender" class="com.navercorp.pinpoint.web.alarm.DingTalkAndWechatMessageSender" >
        <property name="dingTalkUrl" value="#{pinpointWebProps['web.ding.talk.url'] ?: ''}"/>
    </bean>

步骤6-测试验证

查看是否赋值成功:
file

调用业务接口,我伪造了一个慢请求接口

file

验证日志:

file

验证钉钉消息:

file

总结

如上,就是将pinpoint告警接入钉钉群的全部过程,若有任何疑问,欢迎来交流讨论。
通过这篇文章,来复测以下,是否掌握了以下知识呢:
1、如何发送消息接入钉钉群聊。

2、如何编译class增量发布。(虽然并不是很推荐这种方式,但是为了提高效率所以这样用了)

3、如何实现pinpoint的告警接口。

4、pinpoint告警类读取配置文件方式。

推送告警到微信群,就不再文章讲述了,有需求的小伙伴可以自行研究实现。希望大家多多总结,提高自己。

欢迎来公众号【侠梦的开发笔记】 ,回复干货,领取精选学习视频一份
查看原文

赞 1 收藏 1 评论 0

认证与成就

  • 获得 8 次点赞
  • 获得 24 枚徽章 获得 0 枚金徽章, 获得 5 枚银徽章, 获得 19 枚铜徽章

擅长技能
编辑

(゚∀゚ )
暂时没有

开源项目 & 著作
编辑

(゚∀゚ )
暂时没有

注册于 2016-08-15
个人主页被 899 人浏览