0%

声明:如果本文有错误,希望指出。

ReentrantLock 位于 java.util.concurrent.locks 包下,它实现了 Lock 接口和 Serializable 接口。

ReentrantLock 默认非公平,但可实现公平的(构造器传true),悲观,独享,互斥,可重入,重量级锁。ReentrantLock 就是一个普通的类,它是基于 AQS(AbstractQueuedSynchronizer)来实现的。

ReetrantLock 基本用法

构造方法

1
2
3
4
5
6
7
public ReentrantLock() {
sync = new NonfairSync();
}
//判断是否开启公平锁
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}

ReentrantLock 提供公平锁和非公平锁的构造方法,默认构造方法是非公平锁。

NonfairSync 和 FairSync 都是 ReentrantLock 的内部类,继承 Sync 类。

关于公平锁和非公平锁的区别,主要是在多线程情况下,获取锁的机会是否相同。

阅读全文 »

kubernetes 是一个开源的,用于管理云平台中多个主机上的容器化的应用,Kubernetes的目标是让部署容器化的应用简单并且高效(powerful),Kubernetes提供了应用部署,规划,更新,维护的一种机制。

Kubernetes 有多种安装方式,比如麻烦无比的二进制安装方式,本篇文章主要讲解如何使用 Kubeadm 来安装 Kubernetes 集群。

前期准备工作

系统 角色 IP 主机名 内存(2G以上)
Centos 7 master 192.168.60.11 k8s-master-11.cn 2G
Centos 7 node 192.168.60.16 k8s-node-16.cn 2G
Centos 7 node 192.168.60.170 k8s-node-170.cn 2G

本文搭建的只是 Kubernetes 集群,而不是 Kubernetes 高可用集群,高可用集群需要多台 master 节点来避免 master 节点中某台服务可能宕机。下面这些前期准备,需要在所以服务器上执行一遍。由于本文使用的操作系统是 Centos,其中你是 Ubuntu 用户,请查看官方具体的操作

根据表格中的信息修改服务器主机名的,具体的主机名请根据自己实际命名修改。需要注意的是,必须保证每台服务器的主机名唯一。

1
2
3
4
5
# 修改主机名
hostnamectl set-hostname k8s-master-11.cn

# 查看主机名
hostname

向每台服务器上的 /etc/hosts 文件添加一下配置。因为只有三台服务器,我们直接修改 hosts 文件来指定,如果是很大的集群,每次添加一个节点,都要修改每台服务器的 hosts 文件,这样会很麻烦,所以可以选择使用dns去解析,但是我们这里只有几台服务器,就直接修改 hosts 文件即可。

1
2
3
192.168.60. k8s-master-252.cn
192.168.60.227 k8s-node-227.cn
192.168.60.15 k8s-node-15.cn

关闭防火墙

阅读全文 »

本文是读《深入剖析 Kubernetes》的笔记

在很久之前写过几篇关于 Docker 的文章,当时也正处于自己接触 Docker 不久,后来一直没在实际环境中使用过。最近公司要搞一套服务,需要使用 Kubernetes 来编排所以的容器。然后就重新拾起 Docker 的内容,最近也在学习 Kubernetes,最近在看极客时间的《深入剖析Kubernetes》,感兴趣的可以从下面地址购买。

极客时间-Kubernetes

在之前知道 Docker 容器使用了 Namespace 和 Ggroups 技术,但是对于这两个东西具体是什么,还真没搞清楚过。真心推荐极客时间的这么课,还是非常不错的。

容器技术的核心功能,就是通过约束和修改进程的动态表现,从而为其创造一个”边界“。

对于Docker等大多数Linux容器来说,Cgroups是用来制造约束的主要手段,Namespace则是用来修改进程试图的主要方法

Namespace

namespace 是 Linux 内核用来隔离内核资源的方式。通过 namespace 可以让一些进程只能看到与自己相关的一部分资源,而另外一些进程也只能看到与它们自己相关的资源,这两拨进程根本就感觉不到对方的存在。具体的实现方式是把一个或多个进程的相关资源指定在同一个 namespace 中。

Linux namespaces 是对全局系统资源的一种封装隔离,使得处于不同 namespace 的进程拥有独立的全局系统资源,改变一个 namespace 中的系统资源只会影响当前 namespace 里的进程,对其他 namespace 中的进程没有影响。

使用过 Docker 的应该知道,如果我们使用 docker exec -it 进入容器,然后使用ps命令,你会看到以下信息:

阅读全文 »

最近在测试环境登录的时候,突然出现了下面的异常。

本来这条SQL只查询一条数据的,但是在错误日志中,发现后面莫名其妙的多了一个LIMIT。第一个反应是不是这个查询的前面用错了PageHelper的分页功能,但是查看了项目中的这处查询,发现LIMIT只是在SQL里面写死了LIMIT 1。我就懵逼了,问了下同事,同事说以前项目也出现过这种抽风的现象,然后我一脸黑人问号。

最近几天大致的看了下PageHelper(文章后面给了源码地址)这个分页工具的源码。在项目中,根据PageHelper的文档,一般会使用以下前两种分页方法(官方文档也推荐这样使用,其实我本人使用最多的是第三种):

1
2
3
4
5
6
7
8
PageHelper.startPage(1, 10);
List<User> list = userMapper.selectIf(1);
//或者
PageHelper.offsetPage(1, 10);
List<User> list = userMapper.selectIf(1);

//jdk8 lambda用法
Page<User> page = PageHelper.startPage(1, 10).doSelectPage(()-> userMapper.selectGroupBy());

从上面我们可以看出,分页的数据和SQL查询是分开的,那么PageHelper是如何做到把分页数据准确的拼接到SQL后面呢?如果你在源码中点开 PageHelper 这个类,你会发现这个类是继承 PageMethod 以及实现 Dialect 接口的。在 PageMethod 中,定义了一个 protected static final ThreadLocal<Page> LOCAL_PAGE = new ThreadLocal<Page>(); 的全局常量,然后在源码中,你会发现PageHelper只是把你的分页数据,放到了ThreadLocal LOCAL_PAGE中了,然后就没然后了。

虽然PageHelper还有别的一些设置,比如数据库方言的选择等,但是我们今天不讨论这些,如果自己感兴趣的话,可以去翻翻源码。如果仔细的看下源码,会发现一个 PageInterceptor 类,这个类实现的是 ibatisInterceptor。在拦截器中,PageHelper 判断是查询语句是否需要分页,是否需要count查询,分页数据拼接查询等操作。下面三个截图大致的说明了一些拦截器中的一些原理(作者源码注释还是挺好理解的)。



阅读全文 »

Linux 传统的数据传输,一般需要设计到数据的4次拷贝,4次copy,其中四次copy,其中的两次用户态和内核态之间Copy需要CPU参与,两次内核态和IO设备间的copy为DMA方式不需要CPU的参与。零拷贝技术主要就是减少用户态和内核态间copy数据次数。

比如我们现在需要把实现一个场景:从一个文件中读取数据并将数据发送到另一台服务器上。从上图中看出:

  • 应用程序调用 read 方法,这里会涉及到一次上下文切换(用户态->内核态),底层采用DMA 读取磁盘文件数据,并把内容存储到内核地址空间的读取缓存。
  • 由于应用程序无法读取内核地址空间的数据,如果需要程序操作这些数据,必须吧数据从读取缓冲区拷贝到用户缓冲区。这时,read()调用返回,引发一次上下文切换(内核态->用户态),这样就把数据拷贝到用户地址空间缓冲区了。这样应用程序就可以操作这些数据了。
  • 我们的目的是需要把数据发送到另外一个服务器,调用Socket的send()方法,这时候又涉及到一次上下文的切换(用户态->内核态),同事文件数据被进行第三次拷贝,再次从用户地址空间拷贝到内核地址空间缓冲区。
  • send() 调用返回,引发第四次上下文切换,同时进行第四次的数据拷贝,通过DMA把数据从目标套接字相关的缓存区传输到协议引擎进行发送。

在上面的图中,1和4是有DMA负责,不需要CPU参与,但是过程2和3需要CPU的参与。从这样流程中,我们会发现,如果不需要再应用程序中操作文件数据,其中2和3的步骤是多余的,直接把内核态数据读取后直接拷贝到套接字相关的缓冲区,就能提升性能。这就是零拷贝技术。

零拷贝技术可以减少数据拷贝和共享总线操作的次数,消除传输数据在存储器之间不必要的中间拷贝次数,从而有效地提高数据传输效率。零拷贝技术减少了用户应用程序地址空间和操作系统内核地址空间之间因为上下文切换而带来的开销。

Java 层实现零拷贝

阅读全文 »

RabbitMQ 相关概念介绍

RabbitMQ 整体上是一个生产者与消费者模型,主要负责接收、储存和转发消息。下面的图是RabbitMQ 的整体模型架构图:

RabbitMQ 的一些角色

  • Producer:生产者,就是投递消息的一方;
  • Consumer:消费者,接收消息的一方;
  • Broker:消息中间件的服务节点;
  • 队列:是 RabbitMQ的内部对象,用于储存消息。RabbitMQ 的生产者生产的消息并最终投递到队列中,消费者可以从队列中获取消息并消费。多个消费者可以订阅同一队列,这时队列中的消息会被平均分摊(Round-Robin,即轮询)。

交换器、路由键和绑定

交换器(Exchange)

交换器:在RabbitMQ 中,生产者实际把消息发送个 Exchange,由交换器将消息路由到一个或者多个队列中。如果路由不到,或许会返给生产者,或许直接丢弃。

阅读全文 »

消息中间件的作用

消息中间件的作用可以概括如下:

  • 解耦:消息中间件在处理过程中间插入了一个隐含的、基于数据的接口层,两边的处理过程都要事先这一接口,这允许你独立地扩展或修改两边的处理过程,只要确保他们遵守同样的接口约束即可。
  • 冗余(储存):有些情况下,处理数据的过程会失败。消息中间件可以把数据持久化直到它们已经完全处理,通过这一范式规避了数据丢失风险。
  • 扩展性:因为消息中间件解耦了应用的处理过程,所以提高了消息入队和处理的效率和容易的,只要另外增加处理过程即可,不需要改变代码,也不需要调节参数。
  • 消峰:在访问量剧增的情况下,应用仍然需要继续发挥作用,但是这样的突发流量并不常见。使用消息中间件能够使关键组件支撑突发访问压力,不会因为突发的超负荷请求而完全崩溃。
  • 可恢复性:即使一个处理消息的进程挂掉,加入消息中间件中的消息仍然可以在系统恢复后进行处理。
  • 顺序保证:消息中间件支持一定程度上的顺序性
  • 缓冲:消息中间件通过一个缓冲层来帮助任务最高效率的执行,写入消息中间件的处理会尽可能快速。该缓冲层有助于控制和优化数据流经过系统的速度。
  • 异步通信:消息中间件提供了异步处理机制。

AMQP

AMQP,即 Advanced Message Queuing Protocol,一种提供统一消息服务的应用层标准高级
消息队列协议,是应用层协议的一个开放标准,为面向消息的中间件设计。基于此协议的客户端和消息中间件可传递消息,并不受客户端/中间件不同产品,不同的开放语言等条件的限制。基于此协议的消息中间件有RabbitMQ。

各种MQ的比较

目前业界有很多MQ产品,比如 RabbitMQ、RocketMQ、Kafka、ActivceMQ等。其中ActiceMQ现在社区活跃度不是很高,已经被很多人弃用了。其中RabbitMQ 是基于erlang语言的,虽然其实开源的,但是如果需要定制化的话,维护是一件很麻烦的事情。Kafka 和 RocketMQ ,前者是Scala,后者是阿里出品的,基于Java开发的。所以,我们一般选择MQ的时候,主要从RabbitMQ、Kafka、RocketMQ这几个主流MQ中选择一个适合的。

Kafka

Kafka 主要定位在日志等方面,因为Kafka 设计的初衷就是为了处理日志。

阅读全文 »

最近一段时间在了解领域模型,之前拜读了下《领域驱动设计——软件核心复杂性应对之道》,结果看的云里雾里,晦涩的语句,不明所以的专业术语,加上翻译导致的语句流畅性,可以说观看体验并不是很好。然后同事推荐我先看《实现领域驱动设计》这本书,但是对于这种软件设计的书,稍微之前那本好点了。以前都是“talk is cheap, show me the code”,加上自己在这方面没啥经验积累,看的过程中,没啥共鸣。

接下来主要是自己在看的过程中的一些笔记和理解。

领域模型之贫血模型和充血模型

贫血模型:Model中,仅包含状态属性,不包含个行为,采用这种设计时,需要分离出DB曾,左门用语数据库操作。现在的web软件开发主要使用的就是贫血模式。
优点:系统层次结构清楚,各层之间单向依赖,缺点是不够面向对象

充血模型:Model中即包含状态,也包含行为,是最符合面向对象的设计方式。
优点面向对象,缺点比较复杂,对技术要求更高。
Spring data 的 Repository 是对充血模型的最佳实践

领域、子域和限界上下文

在DDD领域中,一个领域被分为若干个子域,领域模型在界限上下文中进行开发。

限界上下文是一个显示边界,灵越模型便存在边界之内。在边界内,通用语言中的所有术语和词组都有特定的含义,而模型需要准确的反映通用语言。

架构风格

在选择使用框架时,需要明确其使用目的:建立一种可以表达领域模型的实现并且用它来解决重要问题。

阅读全文 »

数据库是一个多用户共享的资源,这样的话对于多个用户在存取同一数据的时候,就会出现问题,举个最经典的问题—-票务系统,如何保证数据的正确性。当只剩下最后1张票的时候,两个用户同时取到数据并更新,那么最后是谁买到票了呢?

数据库事务

数据库事务是指单个逻辑工作单元执行一系列操作,要么完成执行,要么完成不执行。数据库事务必须满足ACID(原子性、一致性、隔离性、持久性)。

  • 原子性:对于其数据的修改,要么全部执行,要么完全不执行。原子性消除了系统处理操作子集的可能。
  • 一致性:事务完成时,必须是所有的数据都保持一致
  • 隔离性:由并发事务所做的修改必须与任何其他事务并发事务所做的修改隔离
  • 持久性:完成事务后,对系统的修改时永久性的

事务隔离级别

  • 未提交读:当前事务未提交、其他事务也能读到
  • 提交读:当前事务提交之后,其他事务才能看到
  • 可重复读:该级别解决了同一事务多次读取同样记录的结果是一致的。但是理论上,还是无法解决另一个幻读问题。幻读 是指当前事务读取某个范围内的记录时,其他事务在该范围又加入新的纪录,之前的事务再次从该范围读取数据时,会产生幻行。这个的换行是对于 insert 操作来说的,对于 update 操作能保证没有幻行问题。
  • 可串行化:最高的事务隔离级别。强制事务串行化执行,避免了幻读问题,这种级别会在数据的每一行都加锁,会产生大量的超时和锁竞争,实际中很少用到,除非要确保数据的一致性且没有并发问题

今天我们所讲的数据库是MySQL。InnoDB支持行/表级锁,默认行级锁。

共享锁

阅读全文 »

现在国内对于数据库分库分表的开源方案,主要是mycat和sharding-sphere,本文主要是自己对于sharding-sphere使用的一些记录。

sharding-sphere简单介绍

Sharding-JDBC 采用在 JDBC 层扩展分库分表,支持读写分离,是一个以 jar 形式提供服务的轻量级组件,其核心思路是小而美地完成最核心的事情,基于 JDBC 层进行分片的好处是轻量、简单、兼容性好以及无需额外的运维工作。缺点是无法跨语言,目前仅支持 Java。

Sharding-Sphere的3个产品的数据分片主要流程是一致的。核心思想是:SQL解析 => 执行器优化 => SQL路由 => SQL改写 => SQL执行 => 结果归并的流程组成。(详情查看官网文档)

分库分表

开发环境: 项目搭建使用的是 Spring Boot + Sharding-Sphere + MyBatis。

注意:Sharding-Sphere 对于Spring-Boot 好像还不支持2.x版本以上,请选择1.x版本,详细源码,请查看相应的demo。

分别创建2个数据库,然后创建相应的表,创建数据库DDL如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
CREATE TABLE `t_order` (
`order_id` bigint(20) NOT NULL AUTO_INCREMENT,
`status` varchar(255) DEFAULT NULL,
`user_id` int(11) DEFAULT NULL,
PRIMARY KEY (`order_id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=344805296301932545 DEFAULT CHARSET=utf8;

CREATE TABLE `t_order_item` (
`order_item_id` bigint(20) NOT NULL,
`order_id` bigint(20) DEFAULT NULL,
`status` varchar(255) DEFAULT NULL,
`user_id` int(11) DEFAULT NULL,
PRIMARY KEY (`order_item_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
阅读全文 »