简栈

拥抱AI,持续成长

背景

上一篇只是单纯的从原理上以及控制台上去实践系统之间的打通,但是如果能从页面上去看每一个请求日志的链路情况就更好了。其实zipkin是提供了一个UI后台管理给到我们的。

**注意点:**关于 Zipkin 的服务端,在使用 Spring Boot 2.x 版本后,官方就不推荐自行定制编译了,反而是直接提供了编译好的 jar 包来给我们使用。具体请查阅:https://zipkin.io/pages/quickstart.html (最直接、权威的就是阅读官方网站)

1
2
curl -sSL https://zipkin.io/quickstart.sh | bash -s
java -jar zipkin.jar

快速上手

Zipkin 分为两端,一个是 Zipkin 服务端,一个是 Zipkin 客户端,客户端也就是微服务的应用。
客户端会配置服务端的 URL 地址,一旦发生服务间的调用的时候,会被配置在微服务里面的 Sleuth 的监听器监听,并生成相应的 Trace 和 Span 信息发送给服务端。
发送的方式主要有两种,一种是 HTTP 报文的方式,还有一种是消息总线的方式如 RabbitMQ。

不论哪种方式,我们都需要:

  • 一个 Eureka 服务注册中心,这里我们就用之前的 eureka 项目来当注册中心。
  • 一个 Zipkin 服务端。
  • 三个微服务应用,trace-atrace-btrace-c,其中 trace-a 中有一个 REST 接口 /trace-a,调用该接口后将触发对 trace-b 应用的调用,最后调用trace-c。其实2个服务就够了~

环境说明

也是因为一些环境问题,总是导致ui后管一直没有数据,这次参考了很多文章一级官网网址来操作的,才得以显示数据。

阅读全文 »

前言

通过我之前的Tomcat系列文章,相信看我博客的同学对Tomcat应该有一个比较清晰的了解了,在前几篇博客我们讨论了Tomcat在SpringBoot框架中是如何启动的,讨论了Tomcat的内部组件是如何设计以及请求是如何流转的,那么我们这篇博客聊聊Tomcat的异步Servlet,Tomcat是如何实现异步Servlet的以及异步Servlet的使用场景。

手撸一个异步的Servlet

我们直接借助SpringBoot框架来实现一个Servlet,这里只展示Servlet代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
@WebServlet(urlPatterns = "/async",asyncSupported = true)
@Slf4j
public class AsyncServlet extends HttpServlet {

ExecutorService executorService =Executors.newSingleThreadExecutor();

@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//开启异步,获取异步上下文
final AsyncContext ctx = req.startAsync();
// 提交线程池异步执行
executorService.execute(new Runnable() {


@Override
public void run() {
try {
log.info("async Service 准备执行了");
//模拟耗时任务
Thread.sleep(10000L);
ctx.getResponse().getWriter().print("async servlet");
log.info("async Service 执行了");
} catch (IOException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
//最后执行完成后完成回调。
ctx.complete();
}
});
}

上面的代码实现了一个异步的Servlet,实现了doGet方法注意在SpringBoot中使用需要再启动类加上@ServletComponentScan注解来扫描Servlet。既然代码写好了,我们来看看实际运行效果。

http://static.cyblogs.com/async%20Servlet%20result.png

我们发送一个请求后,看到页面有响应,同时,看到请求时间花费了10.05s,那么我们这个Servlet算是能正常运行啦。有同学肯定会问,这不是异步servlet吗?你的响应时间并没有加快,有什么用呢?对,我们的响应时间并不能加快,还是会取决于我们的业务逻辑,但是我们的异步servlet请求后,依赖于业务的异步执行,我们可以立即返回,也就是说,Tomcat的线程可以立即回收,默认情况下,Tomcat的核心线程是10,最大线程数是200,我们能及时回收线程,也就意味着我们能处理更多的请求,能够增加我们的吞吐量,这也是异步Servlet的主要作用。

异步Servlet的内部原理

了解完异步Servlet的作用后,我们来看看,Tomcat是如何是先异步Servlet的。其实上面的代码,主要核心逻辑就两部分,final AsyncContext ctx = req.startAsync() ctx.complete()那我们来看看他们究竟做了什么?

阅读全文 »

学习东西要知行合一,如果只是知道理论而没实践过,那么掌握的也不会特别扎实,估计过几天就会忘记,接下来我们一起实践来学习Spring事务的传播属性。

传播属性

传播属性定义的是当一个事务方法碰到另一个事务方法时的处理行为,一共有七种行为,定义如下

传播性 描述
PROPAGATION_REQUIRED 0 支持当前事务,如果没有就新建事务
PROPAGATION_SUPPORTS 1 支持当前事务,如果没有就不以事务的方式运行
PROPAGATION_MANDATORY 2 支持当前事务,如果当前没事务就抛异常
PROPAGATION_REQUIRES_NEW 3 无论当前是否有事务,都会新起一个事务
PROPAGATION_NOT_SUPPORTED 4 不支持事务,如果当前存在事务,就将此事务挂起不以事务方式运行
PROPAGATION_NEVER 5 不支持事务,如果有事务就抛异常
PROPAGATION_NESTED 6 如果当前存在事务,在当前事务中再新起一个事务

其实只看概念的话已经很直截了当了说明了每个传播性的作用,此时我们再用具体的例子演示一下每个传播性属性下的行为。

此次演示我们使用的是H2数据库,这个数据库是作用在内存里面的,所以对于我们演示事务效果来说正好,无需我们在进行其他的配置了,我们新建一个表。将下面语句放在schema.sql文件里面即可,SpringBoot程序在启动的时候就会自动为我们在内存里面建立这样的一个表。

1
CREATE TABLE FOO (ID INT IDENTITY, BAR VARCHAR(64));

演示之前我们会定义两个类FooServiceBarService。我们使用BarService 里面的方法进行调用FooService 中的方法。

环境准备

在进行事务演示之前,其实可以分为以下几种情况,根据排列组合,我们可以得出以下八种情况

阅读全文 »

Zipkin服务追踪原理

创造一些追踪标识符(tracingId,spanId,parentId),最终将一个request的流程树构建出来,各业务系统在彼此调用时,将特定的跟踪消息传递至zipkin,zipkin在收集到跟踪信息后将其聚合处理、存储、展示等,用户可通过web UI方便获得网络延迟、调用链路、系统依赖等等。

http://static.cyblogs.com/140563-20170613131120462-1419921670.png

transport作用:收集被trace的services的spans,并将它们转化为zipkin common Span,之后把这些Spans传递的存储层

collector会对一个到来的被trace的数据(span)进行验证、存储并设置索引(Cassandra/ES-search/Memory)

Zipkin基本概念&核心数据结构
  • Annotation(用途:用于定位一个request的开始和结束,cs/sr/ss/cr含有额外的信息,比如说时间点):
    • cs:Client Start,表示客户端发起请求一个span的开始
    • sr:Server Receive,表示服务端收到请求
    • ss:Server Send,表示服务端完成处理,并将结果发送给客户端
    • cr:Client Received,表示客户端获取到服务端返回信息一个span的结束,当这个annotation被记录了,这个RPC也被认为完成。客户端调用时间=cr-cs,服务端处理时间=sr-ss。
  • Span:一个请求(包含一组Annotation和BinaryAnnotation);它是基本工作单元,一次链路调用(可以是RPC,DB等没有特定的限制)创建一个span,通过一个64位ID标识它。
    • span通过还有其他的数据,例如描述信息,时间戳,key-value对的(Annotation)tag信息,parent-id等,其中parent-id可以表示span调用链路来源,通俗的理解span就是一次请求信息
  • Trace:类似于树结构的Span集合,表示一条调用链路,存在唯一标识通过traceId(全局的跟踪ID,是跟踪的入口点,根据需求来决定在哪生成traceId)、spanId(请求跟踪ID,比如一次rpc等)和parentId(上一次 请求跟踪ID,用来将前后的请求串联起来),被收集到的span会汇聚成一个tree,从而提供出一个request的整体流程。

整个描述:在一次Trace中,每个服务的每一次调用,就是一个基本工作单元,就像上图中的每一个树节点,称之为span。每一个span都有一个id作为唯一标识,同样每一次Trace都会生成一个traceId在span中作为追踪标识,另外再通过一个parentId标明本次调用的发起者(就是发起者的span-id)。当span有了上面三个标识后,就可以很清晰的将多个span进行梳理串联,最终归纳出一条完整的跟踪链路。

代码实践操作

无存储方式,只需要控制台
阅读全文 »

什么是mysql的主从复制?

MySQL 主从复制是指数据可以从一个MySQL数据库服务器主节点复制到一个或多个从节点。MySQL 默认采用异步复制方式,这样从节点不用一直访问主服务器来更新自己的数据,数据的更新可以在远程连接上进行,从节点可以复制主数据库中的所有数据库或者特定的数据库,或者特定的表。

Mysql复制原理

原理:

http://static.cyblogs.com/MySQL主从同步.jpg

(1)Master服务器将数据的改变记录二进制Binlog日志,当Master上的数据发生改变时,则将其改变写入二进制日志中;

(2)Slave服务器会在一定时间间隔内对Master二进制日志进行探测其是否发生改变,如果发生改变,则开始一个I/OThread请求Master二进制事件

(3)同时主节点为每个I/O线程启动一个Dump线程,用于向其发送二进制事件,并保存至从节点本地的中继日志中,从节点将启动SQL线程从中继日志中读取二进制日志,在本地重放,使得其数据和主节点的保持一致,最后I/OThreadSQLThread将进入睡眠状态,等待下一次被唤醒。

Undo log与Redo log原理分析

Undo log原理
阅读全文 »

获取mysql镜像

1
2
3
4
➜  ~  docker pull mysql
➜ ~ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
mysql latest d435eee2caa5 3 weeks ago 456MB

启动mysql

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
➜  ~  docker run -itd --name docker-mysql-master -v /Users/chenyuan/Data/docker/mysql-data-master:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=root -p 33061:3306 mysql
88820868af121cbac02f48a8c8e5c9eae5c6cf7241eefd3646634e14526a940f
➜ ~ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
88820868af12 mysql "docker-entrypoint.s…" About a minute ago Up About a minute 33060/tcp, 0.0.0.0:33061->3306/tcp docker-mysql-master
➜ ~ docker exec -it 88820868af12 bash
root@88820868af12:/#
root@88820868af12:/# mysql -uroot -p
Enter password:
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 8
Server version: 8.0.18 MySQL Community Server - GPL

Copyright (c) 2000, 2019, Oracle and/or its affiliates. All rights reserved.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql>

确定挂载的mysql-data文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
➜  mysql-data-master  ll
total 356592
drwxr-x--- 12 chenyuan staff 384B Dec 19 17:46 #innodb_temp
-rw-r----- 1 chenyuan staff 1.2K Dec 19 17:46 88820868af12.err
-rw-r----- 1 chenyuan staff 56B Dec 19 17:45 auto.cnf
-rw-r----- 1 chenyuan staff 2.9M Dec 19 17:46 binlog.000001
-rw-r----- 1 chenyuan staff 155B Dec 19 17:46 binlog.000002
-rw-r----- 1 chenyuan staff 32B Dec 19 17:46 binlog.index
-rw------- 1 chenyuan staff 1.6K Dec 19 17:45 ca-key.pem
-rw-r--r-- 1 chenyuan staff 1.1K Dec 19 17:45 ca.pem
-rw-r--r-- 1 chenyuan staff 1.1K Dec 19 17:45 client-cert.pem
-rw------- 1 chenyuan staff 1.6K Dec 19 17:45 client-key.pem
-rw-r----- 1 chenyuan staff 5.3K Dec 19 17:46 ib_buffer_pool
-rw-r----- 1 chenyuan staff 48M Dec 19 17:46 ib_logfile0
-rw-r----- 1 chenyuan staff 48M Dec 19 17:45 ib_logfile1
-rw-r----- 1 chenyuan staff 12M Dec 19 17:46 ibdata1
-rw-r----- 1 chenyuan staff 12M Dec 19 17:46 ibtmp1
drwxr-x--- 8 chenyuan staff 256B Dec 19 17:46 mysql
-rw-r----- 1 chenyuan staff 29M Dec 19 17:46 mysql.ibd
drwxr-x--- 105 chenyuan staff 3.3K Dec 19 17:45 performance_schema
-rw------- 1 chenyuan staff 1.6K Dec 19 17:45 private_key.pem
-rw-r--r-- 1 chenyuan staff 452B Dec 19 17:45 public_key.pem
-rw-r--r-- 1 chenyuan staff 1.1K Dec 19 17:45 server-cert.pem
-rw------- 1 chenyuan staff 1.6K Dec 19 17:45 server-key.pem
drwxr-x--- 3 chenyuan staff 96B Dec 19 17:46 sys
-rw-r----- 1 chenyuan staff 12M Dec 19 17:46 undo_001
-rw-r----- 1 chenyuan staff 10M Dec 19 17:46 undo_002
➜ mysql-data-master pwd
/Users/chenyuan/Data/docker/mysql-data-master

Binlog配置

查看binlog日志的地方,通过命令查看。因为没有设置过所以看不到。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
mysql> mysql> show variables like '%datadir%';
Empty set (0.01 sec)

root@88820868af12:/etc/mysql# pwd
/etc/mysql
root@88820868af12:/etc/mysql# ls -l
total 12
drwxrwxr-x 1 root root 4096 Nov 23 01:48 conf.d
-rw-rw-r-- 1 root root 1174 Nov 23 01:48 my.cnf
-rw-r--r-- 1 root root 1469 Sep 20 09:04 my.cnf.fallback

# 后面设置好后,就能看到了。
mysql> show variables like '%datadir%';
+---------------+------------------------+
| Variable_name | Value |
+---------------+------------------------+
| datadir | /usr/local/mysql/data/ |
+---------------+------------------------+
1 row in set (0.00 sec)

当前容器提交为镜像

阅读全文 »

MySQL锁分类

每次在听别人说锁的时候,是不是会有点儿晕?(一会儿排它锁,一会儿GAP锁…)因为你站在不同的角度来说,它的名字就会不同。根据我们DB的引擎、隔离级别不同,导致的锁的情况也会不同

下面根据几种不同的类型对锁做一个划分:

力度划分:

  • **表级锁:**表级锁是MySQL中锁定粒度最大的一种锁,表示对当前操作的整张表加锁,它实现简单,资源消耗较少,被大部分MySQL引擎支持。最常使用的MYISAM与INNODB都支持表级锁定,开销小,加锁快,粒度大,锁冲突概率大,并发度低,适用于读多写少的情况。

  • **页级锁:**页级锁是MySQL中锁定粒度介于行级锁和表级锁中间的一种锁。表级锁速度快,但冲突多,行级冲突少,但速度慢。所以取了折衷的页级,一次锁定相邻的一组记录。BDB支持页级锁。

  • **行级锁:**行级锁是Mysql中锁定粒度最细的一种锁,表示只针对当前操作的行进行加锁。行级锁能大大减少数据库操作的冲突。其加锁粒度最小,但加锁的开销也最大。Innodb存储引擎,默认选项。

模式划分:

  • **记录锁:**其实很好理解,对表中的记录加锁,叫做记录锁,简称行锁。
  • **GAP锁:**只在RR和Serializable级别下生效.通过gap锁防止其他事务在一定区间插入、删除、修改,来避免幻行问题。
  • **Next-key锁:**是 MySQL 的 InnoDB 存储引擎的一种锁实现,MVCC 不能解决幻读的问题,Next-Key Locks 就是为了解决这个问题而存在的。
  • 意向锁:意向锁是一种不与行级锁冲突表级锁,这一点非常重要。意向锁分为两种意向共享锁(intention shared lock, IS)与意向排他锁(intention exclusive lock, IX)。
  • **插入意向锁:**普通的Gap Lock 不允许 在 (上一条记录,本记录) 范围内插入数据,插入意向锁Gap Lock 允许 在 (上一条记录,本记录) 范围内插入数据。

机制划分:

  • **悲观锁:**顾名思义,就是很悲观,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会block直到它拿到锁。
  • **乐观锁:**顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在提交更新的时候会判断一下在此期间别人有没有去更新这个数据。乐观锁适用于读多写少的应用场景,这样可以提高吞吐量。

兼容性划分:

阅读全文 »

环境准备

前面有几篇文章对于MySQL主从搭建做了一些铺垫:

文章一:MySQL中Binlog的常用设置

文章二:MySQL主从同步-原理&实践篇

先启动Master与Slave的2台mysql服务器,具体信息如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
➜  ~  docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
8f31266d08fc docker-mysql-master:v1 "/usr/sbin/init" 49 minutes ago Up 49 minutes 0.0.0.0:33063->3306/tcp docker-mysql-client
a579aa381425 docker-mysql-slave:v1 "/usr/sbin/init" 19 hours ago Up 19 hours 0.0.0.0:33062->3306/tcp docker-mysql-slave
a40a40c6bde7 docker-mysql-master:v1 "/usr/sbin/init" 19 hours ago Up 19 hours 0.0.0.0:33061->3306/tcp docker-mysql-master

#进入master
➜ ~ docker exec -it 8166c07dd6c7 bash
[root@8166c07dd6c7 /]#

#进入slave
➜ ~ docker exec -it 208c30295ec9 bash
[root@208c30295ec9 /]#

Master机器(172.17.0.2)

1
2
3
4
5
create user 'master_account'@'%' identified by '123456';  
grant replication slave on *.* to 'master_account'@'%';
flush privileges;

change master to master_host='172.17.0.3',master_user='slave_account',master_password='123456',master_log_file='mysql-bin.000001',master_log_pos=120;

Slave机器(172.17.0.3)

1
2
3
4
create user 'slave_account'@'%' identified by '123456';  
grant replication slave on *.* to 'slave_account'@'%';
flush privileges;
change master to master_host='172.17.0.2',master_user='master_account',master_password='123456',master_log_file='mysql-bin.000008',master_log_pos=862;
阅读全文 »

我一直认为Code Review(代码审查)是软件开发中的最佳实践之一,可以有效提高整体代码质量,及时发现代码中可能存在的问题。包括像Google、微软这些公司,Code Review都是基本要求,代码合并之前必须要有人审查通过才行。

然而对于我观察到的大部分软件开发团队来说,认真做Code Review的很少,有的流于形式,有的可能根本就没有Code Review的环节,代码质量只依赖于事后的测试。也有些团队想做好代码审查,但不知道怎么做比较好。

网上关于如何做Code Review的文章已经有很多了,这里我结合自己的一些经验,也总结整理了一下Code Review的最佳实践,希望能对大家做好Code Review有所帮助。

Code Review有什么好处?

很多团队或个人不做Code Review,根源还是不觉得这是一件有意义的事情,不觉得有什么好处。这个问题要从几个角度来看。

  • 首先是团队知识共享的角度

一个开发团队中,水平有高有低,每个人侧重的领域也有不同。怎么让高水平的帮助新人成长?怎么让大家都对自己侧重领域之外的知识保持了解?怎么能有人离职后其他人能快速接手?这些都是团队管理者关心的问题。

而代码审查,就是一个很好的知识共享的方式。通过代码审查,高手可以直接指出新手代码中的问题,新手可以马上从高手的反馈中学习到好的实践,得到更快的成长;通过代码审查,前端也可以去学习后端的代码,做功能模块A的可以去了解功能模块B的。

可能有些高手觉得给新手代码审查浪费时间,自己也没收获。其实不然,新人成长了,就可以更多的帮高手分担繁重的任务;代码审查中花时间,就少一些帮新人填坑擦屁股的时间;良好的沟通能力、发现问题的能力、帮助其他人成长,都是技术转管理或技术上更上一层楼必不可少的能力,而通过代码审查可以有效的去练习这些方面的能力。

  • 然后是代码质量的角度
阅读全文 »

1.字节码

1.1 什么是字节码?

Java之所以可以“一次编译,到处运行”,一是因为JVM针对各种操作系统、平台都进行了定制,二是因为无论在什么平台,都可以编译生成固定格式的字节码(.class文件)供JVM使用。因此,也可以看出字节码对于Java生态的重要性。之所以被称之为字节码,是因为字节码文件由十六进制值组成,而JVM以两个十六进制值为一组,即以字节为单位进行读取。在Java中一般是用javac命令编译源代码为字节码文件,一个.java文件从编译到运行的示例如图1所示。

图1 Java运行示意图

图1 Java运行示意图

对于开发人员,了解字节码可以更准确、直观地理解Java语言中更深层次的东西,比如通过字节码,可以很直观地看到Volatile关键字如何在字节码上生效。另外,字节码增强技术在Spring AOP、各种ORM框架、热部署中的应用屡见不鲜,深入理解其原理对于我们来说大有裨益。除此之外,由于JVM规范的存在,只要最终可以生成符合规范的字节码就可以在JVM上运行,因此这就给了各种运行在JVM上的语言(如Scala、Groovy、Kotlin)一种契机,可以扩展Java所没有的特性或者实现各种语法糖。理解字节码后再学习这些语言,可以“逆流而上”,从字节码视角看它的设计思路,学习起来也“易如反掌”。

本文重点着眼于字节码增强技术,从字节码开始逐层向上,由JVM字节码操作集合到Java中操作字节码的框架,再到我们熟悉的各类框架原理及应用,也都会一一进行介绍。

1.2 字节码结构

.java文件通过javac编译后将得到一个.class文件,比如编写一个简单的ByteCodeDemo类,如下图2的左侧部分:

图2 示例代码(左侧)及对应的字节码(右侧)

阅读全文 »
0%