scu酱油仔

scu酱油仔 查看完整档案

北京编辑四川大学  |  信息管理与信息系统 编辑小米科技有限公司  |  后端开发 编辑 legacy.gitbook.com/@wangjun-scu 编辑
编辑

没有努力过就没有资格说自己运气不好

个人动态

scu酱油仔 发布了文章 · 5月27日

CDN介绍

CDN介绍

一、简介

CDN中文就是内容分发网络(Content Delivery Network)。其目的是在现有的网络中增加一层网络架构,将网站的内容发布到最接近用户的网络“边缘”,使用户可以就近取得所需的内容,提高用户访问网站的响应速度。有别于镜像,它比镜像更加智能,可以理解为:CDN=镜像(Mirror)+缓存(Cache)+整体负载均衡(GSLB)。

目前CDN都以缓存网站中的静态数据为主,如CSS、JS、图片和静态页面等数据。用户从主站服务器中请求到动态内容后,再从CDN上下载静态数据,从而加速网页数据内容的下载速度。

二、CDN架构

通常来说CDN要达到以下几个目标:

  • 可拓展性:性能可扩展性,应对新增的大量数据、用户和事务的拓展能力。成本可扩展性,用低廉的运营成本提供动态的服务能力和高质量的内容分发;
  • 安全性:强调提供物理设备、网络、软件、数据和服务过程的安全性,减少因为DDos攻击或者其他恶意行为;
  • 可靠性、响应和执行:服务可用性指能够处理可能的故障和用户体验下降的问题,通过负载均衡及时提供网络的容错机制。

访问拥有CDN的网站的静态资源流程:

一个用户访问某个静态文件,如CSS文件,首先向Local DNS服务器发起请求,一般经过迭代解析后回到这个域名的注册服务器去解析,这个域名最终会被指向CDN全局中的DNS负载均衡器,再由这个GTM1来最终分配是哪个地方的访问用户,返回给离这个用户最近的CDN节点。用户拿到DNS解析结果就直接去这个CDN节点访问这个静态文件了。如果这个文件不存在,会回到源站去获取这个文件,并且CDN会本节点上缓存一份,方便下次用户来访问。

三、负载均衡

负载均衡就是将大量的请求通过一定的策略分摊到不同的服务节点。他可以提高服务器的影响速度及利用效率,避免软件或者硬件模块出现单点失效,解决网络拥塞问题,实现地理位置无关性,为用户提供较一致的访问质量。

通常有三种负载均衡架构:链路负载均衡、集群负载均衡和操作系统负载均衡。

  • 链路负载均衡:就是前面提到的CDN架构通过DNS解析成不同的IP,然后用保护根据这个IP来访问不同的目标服务器,链路负载均衡是由DNS的解析来完成的。
  • 集群负载均衡:一般分为硬件负载均衡和软件负载均衡。硬件负载均衡一般使用一台专门的硬件设备来转发请求,关键就是这台昂贵的设备,如F5,通常为了安全 需要一主一备,它的优点就是性能非常好,缺点就是非常贵,一般公司用不起,还有就是不支持动态扩容。软件负载均衡比较普遍,特点就是成本低,直接使用廉价的PC就可以搭建,缺点就是一般一次访问请求要经过多次代理服务器,会增加网络延时。
  • 操作系统负载均衡:利用操作系统级别的软件中断或者硬件中断来达到负载均衡,如可以设置多队列网卡等来实现。

四、CDN动态加速

CDN动态加速技术是当前比较流行的一种优化技术,它的技术原理是在CDN的DNS解析中通过动态的链路探测来寻找回源最好的一条路径,然后通过DNS的调度将所有请求调度到选定的这条路径上回源,从而加快用户访问的效率。

由于CDN节点是遍布全国的,所以用户接入一个CDN节点后,可以选择一条从离用户最近的CDN节点到源站链路最好的路径让用户走。判断最好的路径不一定把耗时当做唯一标准,有时也要考虑网络成本,还有其他网络链路的安全等因素也要综合考虑。

疑问

  1. 如果源站有静态文件更新,如何同步CDN节点?有什么策略?
  • 主动刷新缓存:源站更新后主动刷新CDN缓存;
  • 静态资源重命名:编译静态文件的时候文件名带上时间戳或者序列号,一旦更新,由于文件名不一样,那么CDN就会去源站拉取最新文件;
  • 缓存时间:源站控制CDN的缓存时间,过期资源强制从源站重新拉取;
  • 更改URL:设置CDN在缓存的时候,不忽略参数, 即,缓存的key为完整的URI,如果想要访问到最新的资源,可以通过在请求URI后面带上不同的参数,然后请求到CDN节点的时候会直接回源,拉取到最新的资源。
参考:

《深入分析Java Web技术内幕》

CDN 如何保证数据节点的更新和同步?:https://www.zhihu.com/questio...


  1. Global Traffic Manager,广域流量管理器。
查看原文

赞 0 收藏 0 评论 0

scu酱油仔 发布了文章 · 5月27日

Redis持久化

Redis持久化

一、持久化简介

因为Redis是内存数据库,它将自己的数据库状态储存在内存里面,所以如果不想办法将储存在内存中的数据库状态保存到磁盘里面,那么一旦服务器进程退出,服务器中的数据库状态也会消失不见。

因此Redis 提供了两种持久化方案,RDB (Redis DataBase)和 AOF (Append Only File)。

二、RDB持久化

RDB 是 Redis 默认的持久化方案。RDB持久化可以手动执行也可以根据服务器配置选项定期执行。该功能会将在某个时间点上的数据库状态保存到一个.rdb文件。Redis 重启会通过加载.rdb文件恢复数据。

2.1 RDB手动备份恢复

有两个redis命令可以用于生成rdb文件:

  • save:save命令会阻塞redis服务器进程,直到rdb文件创建完成为止,在服务器进程阻塞期间不能处理任何命令请求;
  • bgsave:bgsave命令会派生出一个子进程,由子进程负责创建rdb文件,父进程继续处理redis命令。

rdb文件的数据恢复工作是在redis启动时自动完成的,没有特殊命令来手动进行恢复工作,值得注意的是由于AOF的更新频率通常会比RDB更新频率高,因此如果服务器开启了AOF持久化那么redis优先使用AOF恢复数据,只有在AOF持久化关闭时才使用RDB恢复数据。

# 阻塞主进程的持久化
127.0.0.1:6379> save
OK
# 使用子进程持久化
127.0.0.1:6379> BGSAVE
Background saving started
# 执行任意一个命令就会生成/var/lib/redis/dump.rdb 文件

# rdb文件恢复
# 查看rdb文件存放位置
127.0.0.1:6379> CONFIG GET dir
1) "dir"
2) "/var/lib/redis"
# 将需要恢复的rdb文件放在/var/lib/redis下
# 重启redis(注意:不要使用service redis-server restart/stop/start重启,否则出现莫名其妙的错误,rdb无法恢复)
redis-cli shutdown
sudo redis-server /etc/redis/redis.conf
# 登录redis-cli就会看到恢复的数据

2.2 RDB配置

/etc/redis/redis.conf 文件中

save 900 1  # 时间策略,服务器在900秒之内,对数据库进行了至少1次修改。
save 300 10  # 时间策略,服务器在300秒之内,对数据库进行了至少10次修改。
save 60 10000  # 时间策略,服务器在60秒之内,对数据库进行了至少1000次修改。
dbfilename dump.rdb   #文件名称
dir /var/lib/redis   #文件保存路径 
stop-writes-on-bgsave-error yes   # 如果持久化出错,主进程是否停止写入 
rdbcompression yes    # 是否压缩 
rdbchecksum yes     # 导入时是否检查 

# 通过save 300 10次略,我们可以在5分钟内插入10个数据,到了五分钟发现的确生成了新的dump.rdb文件,说明配置生效

那么为什么需要配置这么多条规则呢?因为Redis每个时段的读写请求肯定不是均衡的,为了平衡性能与数据安全,我们可以自由定制什么情况下触发备份。所以这里就是根据自身Redis写入情况来进行合理配置。

2.3 RDB优缺点

优点

  • 适合大规模的数据恢复。
  • 如果业务对数据完整性和一致性要求不高,RDB是很好的选择。

缺点:

  • 数据的完整性和一致性不高,因为RDB可能在最后一次备份时宕机了。
  • 备份时占用内存,因为Redis 在备份时会独立创建一个子进程,将数据写入到一个临时文件(此时总占用内存是原来的两倍),最后再将临时文件替换之前的备份文件。所以Redis 的持久化和数据的恢复要选择在夜深人静的时候执行是比较合理的。

三、AOF持久化

Redis 默认不开启。它的出现是为了弥补RDB的不足(数据的不一致性),所以它采用日志的形式来记录每个写操作,并追加到文件中。Redis 重启的会根据日志文件的内容将写指令从前到后执行一次以完成数据的恢复工作。

3.1 AOF持久化步骤

  1. 命令追加:在AOF模式打开的情况下,服务器每执行一次写命令就会以协议格式将执行的写命令追加到aof_buf缓冲区的末尾;
  2. AOF文件的写入和同步:Redis每执行完一次事件循环,就要考虑要不要将缓冲区的数据写入AOF文件,写入同步策略有配置文件的appendfsync决定:appendfsync的值有三个

    1. always:将缓冲区所有内容写入并同步AOF文件;
    2. everysec(默认):将缓冲区内容写入AOF文件,如果上次同步AOF文件的时间与现在间隔超过1秒钟,那么在此对AOF文件进行同步,并且这个同步操作是由一个线程专门执行的;
    3. no:将缓冲区内容写入文件,不对AOF进行同步,何时同步由操作系统来决定。
tips:
为了提高文件的写入效率,在现代操作系统中,当用户调用write函数,将一些数据写入到文件的时候,操作系统通常会将写入数据暂时保存在一个内存缓冲区里面,等到缓冲区的空间被填满、或者超过了指定的时限之后,才真正地将缓冲区中的数据写入到磁盘里面。这种做法虽然提高了效率,但也为写入数据带来了安全问题,因为如果计算机发生停机,那么保存在内存缓冲区里面的写入数据将会丢失。为此,系统提供了fsync和fdatasync两个同步函数,它们可以强制让操作系统立即将缓冲区中的数据写入到硬盘里面,从而确保写入数据的安全性。

3.2 AOF重写机制

因为AOF备份的策略是保存所有的写命令,当写命令不断增加时,AOF文件会越来越大,可能会影响系统性能和恢复数据的性能。为了解决这个问题,Redis提供了AOF文件重写(rewrite)功能。通过该功能,Redis服务器可以创建一个新的AOF文件来替代现有的AOF文件,新旧两个AOF文件所保存的数据库状态相同,但新AOF文件不会包含任何浪费空间的冗余命令,所以新AOF文件的体积通常会比旧AOF文件的体积要小得多。

那么它是如何实现的呢?虽然名字叫做AOF重写,但是新文件并不会读取并分析旧的AOF文件,而是通过读取当前数据库的状态来实现的。过程就是首先从数据库中读取键现在的值,然后用一条命令去记录键值对,代替之前记录这个键值对的多条命令,这就是AOF重写功能的实现原理。

数据不一致的处理

Redis使用一个子进程来处理AOF的重写,这样会导致数据不一致的问题,比如当开始重写的时候数据库只有key1,在重写过程中有增加了key2,那么重写完后AOF中只有key1,就导致了数据的不一致。为了解决这种数据不一致问题,Redis服务器设置了一个AOF重写缓冲区,这个缓冲区在服务器创建子进程之后开始使用,当Redis服务器执行完一个写命令之后,它会同时将这个写命令发送给AOF缓冲区和AOF重写缓冲区。

当子进程完成AOF重写工作之后,它会向父进程发送一个信号,父进程在接到该信号之后,会调用一个信号处理函数,并执行以下工作:

  • 将AOF重写缓冲区中的所有内容写入到新AOF文件中,这时新AOF文件所保存的数据库状态将和服务器当前的数据库状态一致。
  • 对新的AOF文件进行改名,原子地(atomic)覆盖现有的AOF文件,完成新旧两个AOF文件的替换。这个信号处理函数执行完毕之后,父进程就可以继续像往常一样接受命令请求了。

在整个AOF后台重写过程中,只有信号处理函数执行时会对服务器进程(父进程)造成阻塞,在其他时候,AOF后台重写都不会阻塞父进程,这将AOF重写对服务器性能造成的影响降到了最低。

3.3 AOF配置和恢复

/etc/redis/redis.conf 文件中

appendonly yes  # yes表示开启AOF持久化
appendfilename "appendonly.aof" 
appendfsync everysec # 数据写入同步的策略(everysec、always、no)
#设置为yes表示rewrite期间对新写操作不fsync,暂时存在内存中,等rewrite完成后再写入,默认为no
no-appendfsync-on-rewrite no 
#当前AOF文件大小是上次日志重写得到AOF文件大小的二倍时,自动启动新的日志重写过程。
auto-aof-rewrite-percentage 100
#当前AOF文件启动新的日志重写过程的最小值,避免刚刚启动Reids时由于文件尺寸较小导致频繁的重写。
auto-aof-rewrite-min-size 64mb

恢复数据:

# 配置完后重启redis,写入几条数据,发现生成了aof文件
# 先将aof文件移动到其他目录,然后清空所有数据,再移动回来,重启redis后发现数据恢复了。

appendonly.aof其实就是文本文件,所以完全可以手动修改aof文件来恢复成指定的数据:

# file appendonly.aof 
appendonly.aof: ASCII text, with CRLF line terminators
# cat appendonly.aof 
*2
$6
SELECT
$1
0
*3
$3
......此处省略N行
set
$5
name4
$1
4

3.4 AOF优缺点

优点:

数据的完整性和一致性更高

缺点:

因为AOF记录的内容多,文件会越来越大,数据恢复也会越来越慢。

四、总结

  1. Redis 默认开启RDB持久化方式,在指定的时间间隔内,执行指定次数的写操作,则将内存中的数据写入到磁盘中。
  2. RDB 持久化适合大规模的数据恢复但它的数据一致性和完整性较差。
  3. Redis 需要手动开启AOF持久化方式,默认是每秒将写操作日志追加到AOF文件中。
  4. AOF 的数据完整性比RDB高,但记录内容多了,会影响数据恢复的效率。
  5. Redis 针对 AOF文件大的问题,提供重写的瘦身机制。
  6. 若只打算用Redis 做缓存,可以关闭持久化。
  7. 若打算使用Redis 的持久化。建议RDB和AOF都开启。其实RDB更适合做数据的备份,留一后手。AOF出问题了,还有RDB。

问题

  1. RDB和AOF持久化后的文件存储在哪?

RDB文件存储在/var/lib/redis/dump.rdb,实际位置取决于配置文件/etc/redis/redis.conf中的dir /var/lib/redis,也可以在redis-cli中使用命令config get dir获取。

  1. AOF持久化开启后,是否只是记录开启时间点之后的写操作,那之前的数据如何保证备份呢?

AOF重写时会以当前数据库为依据生成所有写入语句。

查看原文

赞 0 收藏 0 评论 0

scu酱油仔 发布了文章 · 4月24日

Java锁Lock的种类

Java锁Lock的种类

我们平时听到用到的锁有很多种:公平锁/非公平锁、可重入锁/不可重入锁、共享锁/排他锁、乐观锁/悲观锁、分段锁、偏向锁/轻量级锁/重量级锁、自旋锁。其实这些都是在不同维度或者锁优化角度对锁的一种叫法,我们在程序中用到的也就那么几种,比如synchronized,ReentrantLock,ReentrantReadWriteLock。

ReentrantLock类

Jdk1.5新增的ReentrantLock类和synchronized关键字一样可以实现线程间同步互斥,但是它在拓展功能上更加强大,比如嗅探锁定多路分支等功能,使用的时候也比synchronized更加灵活。

使用Condition对象可以实现类似synchronized的wait()/notify()/notifyAll()同样的功能。Condition有更好的灵活性,比如可以实现多路通知功能,也就是在一个Lock对象中创建多个Condition(即对象监视器)实例,线程对象可以注册在指定的Condition中,从而可以有选择性的进行线程通知,在调度线程上更加灵活。

在使用notify()/notifyAll()方法进行通知时,被通知的线程是由JVM随机选择的,但是使用ReentrantLock和Condition可以实现“选择性通知”,这个功能是非常重要的,而且在Condition类时默认提供的。

而synchronized就相当于整个Lock对象中只有一个单一的Condition对象,所有的线程都注册在它一个对象身上,线程开始notifyAll时,需要通知所有的waiting线程,没有选择权,会出现相当大的效率问题。

signal()和signalAll()区别

signalAll通知所有使用了同一个Condition对象的线程。signal()通知所有使用了Condition对象的某一个线程,通过源码可以看到通知的线程是位于队首的那个。

锁的类型

锁/类型公平/非公平锁可重入/不可重入锁共享/独享锁乐观/悲观锁
synchronized非公平锁可重入锁独享锁悲观锁
ReentrantLock都支持可重入锁独享锁悲观锁
ReentrantReadWriteLock都支持可重入锁读锁-共享,写锁-独享悲观锁

一. 公平锁和非公平锁

公平锁表示线程获取锁顺序是按照线程加锁的顺序来分配的,即FIFO顺序。而非公平锁就是一种获取锁的抢占机制,是随机获得锁的。有可能后申请的线程比先申请的线程优先获取锁,可能会造成优先级反转或者饥饿现象。

在公平的锁中,如果有另一个线程持有锁或者有其他线程在等待队列中等待这个所,那么新发出的请求的线程将被放入到队列中。而非公平锁上,只有当锁被某个线程持有时,新发出请求的线程才会被放入队列中。

对于ReentrantLock而言,通过构造函数指定该锁是否是公平锁,默认是非公平锁。非公平锁的优点在于吞吐量比公平锁大。

// ReentrantLock的构造器
public ReentrantLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();
}

对于Synchronized而言,也是一种非公平锁。由于其并不像ReentrantLock是通过AQS的来实现线程调度,所以并没有任何办法使其变成公平锁。

参考:

https://blog.csdn.net/z691837...

二. 可重入锁和不可重入锁

可重入锁又名递归锁,直指同一个线程在外层方法获得锁之后,在进入内层方法时,会自动获得锁。ReentrantLockSynchronized都是可重入锁。可重入锁的好处之一就是在一定程度上避免死锁。下面通过构建可重入锁和不可重入锁来详细的了解一下。

首先看一个类的定义:

package com.wangjun.thread.IsReentrantLock;

public class Test {
    
    Lock1 lock = new Lock1();
    
    public static void main(String[] args) throws InterruptedException {
        Test t = new Test();
        t.test1();
    }
    
    public void test1() throws InterruptedException {
        lock.lock();
        System.out.println("test1方法执行...调用test2方法");
        test2();
        lock.unLock();
    }
    
    public void test2() throws InterruptedException {
        lock.lock();
        System.out.println("test2方法执行...");
        lock.unLock();
    }
}

如果Lock1是一个不可重入锁,那么test1执行的时候已经拿到了锁,再调用test2,由于test2一直获取不到锁,因此会进入死锁状态。

我们来看一下将Lock1实现为不可重入锁:

package com.wangjun.thread.IsReentrantLock;

/*
 * 不可重入锁设计
 */
public class Lock1 {
    private boolean lock = false;
    
    public synchronized void lock() throws InterruptedException {
        while(lock) {
            wait();
        }
        lock = true;
    }
    
    public synchronized void unLock() {
        lock = false;
        notify();
    }
}

不可重入锁的弊端可以清晰的看到,那么如何构造一个可重入锁呢?我们来看一下可重入锁Lock2的设计:

package com.wangjun.thread.IsReentrantLock;

public class Lock2 {

    private boolean lock = false;  //记录是否有线程获得锁
    private Thread curThread = null;  //记录获得锁的线程
    private int lockCount = 0;  //记录加锁次数
    
    public synchronized void lock() throws InterruptedException {
        Thread thread = Thread.currentThread();
        //如果已经加锁并且不是等当前线程,那么就等待
        while(lock && thread != curThread) {
            wait();
        }
        
        lock = true;  //线程获得锁
        lockCount++;  //加锁次数+1
        curThread = thread;  //获得锁的线程等于当前线程

    }
    
    public synchronized void unLock() {
        Thread thread = Thread.currentThread();
        // 如果是获得锁的线程调用unLock,那么加锁次数减一
        if(thread == curThread) {
            lockCount--;
            //所有的加锁都释放,通知其他线程可以获得锁了
            if(lockCount == 0) {
                notify();
            }
        }
    }
}

将测试类的:

Lock1 lock = new Lock1();

换成

Lock2 lock = new Lock2();

可以看到线程运行正常,不再造成死锁。在test1加锁后调用test2方法时,由于是同一个线程,所以test2种也会顺利拿到锁,并继续执行。

可重入锁就是要保证:线程可以进入任何一个它已经拥有锁所同步着的代码块。

参考:

https://www.cnblogs.com/dj383...

三. 共享锁和独享锁

共享锁也叫S锁,读锁,该锁可以被多个线程持有;

独享锁也叫X锁,写锁,排他锁,该锁只能被一个线程持有。

共享锁【S锁】
若事务T对数据对象A加上S锁,则事务T可以读A但不能修改A,其他事务只能再对A加S锁,而不能加X锁,直到T释放A上的S锁。这保证了其他事务可以读A,但在T释放A上的S锁之前不能对A做任何修改。

排他锁【X锁】
又称写锁。若事务T对数据对象A加上X锁,事务T可以读A也可以修改A,其他事务不能再对A加任何锁,直到T释放A上的锁。这保证了其他事务在T释放A上的锁之前不能再读取和修改A。

对于ReentrantLock和Synchronized而言,是独享锁,读读、读写、写写的过程都是互斥的。对于ReadWriteLock而言,读锁是共享锁,写锁是独享锁,读锁的共享锁可以保证并发读是非常高效的,在读写锁中,读读不互斥、读写、写写的过程是互斥的。独享锁与共享锁也是通过AQS来实现的,通过实现不同的方法,来实现独享或者共享。

读写锁ReentrantReadWriteLock的应用场景:

读写锁:分为读锁和写锁,多个读锁不互斥,读锁与写锁互斥,这是由jvm自己控制的,我们只要上好相应的锁即可。如果你的代码只读数据,可以很多人同时读,但不能同时写,那就上读锁;如果你的代码修改数据,只能有一个人在写,且不能同时读取,那就上写锁。总之,读的时候上读锁,写的时候上写锁!

ReentrantReadWriteLock会使用两把锁来解决问题,一个读锁,一个写锁。

线程进入读锁的前提条件:

  1. 没有其他线程的写锁

进入写锁的前提条件:

 1. 没有其他线程的读锁

         2. 没有其他线程的写锁

ReentrantReadWriteLock的javaodoc文档中提供给我们的一个很好的Cache实例代码案例:

class CachedData {
  Object data;  //缓存的数据
  volatile boolean cacheValid;  //缓存是否有效
  final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();

  public void processCachedData() {
    rwl.readLock().lock();
    if (!cacheValid) {
      // 再加写锁之前必须先释放读锁,因为进入写锁的条件是没有其他线程的读锁和写锁
      rwl.readLock().unlock();
      rwl.writeLock().lock();
      try {
        // 类似单例模式的DCL双重检查,防止其他线程先拿到写锁对数据进行了缓存,因此要再判断一次
        if (!cacheValid) {
          data = ...
          cacheValid = true;
        }
        // 在释放写锁之前通过获取读锁降级写锁,防止释放写锁后立即被其他线程加上写锁,导致读取脏数据
        rwl.readLock().lock();
      } finally {
        rwl.writeLock().unlock(); // 释放写锁而此时已经持有读锁
      }
    }

    try {
      use(data);
    } finally {
      rwl.readLock().unlock();
    }
  }
}
参考:

https://www.cnblogs.com/liang...

四. 乐观锁和悲观锁

悲观锁:

总是假设最坏的情况,每次拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想要拿到它的数据就会被一直阻塞直到它拿到锁,传统的关系型数据库里面就用到了很多这种锁机制,比如行锁、表锁、读锁、写锁等,都是在操作之前先上锁。再比如java里面的synchronized关键字的实现也是悲观锁。

乐观锁:

顾名思义,很乐观,每次拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会去判断一下别人有没有修改这个数据,可以使用版本号等机制。乐观锁适用于多读的应用场景,这样可以提高吞吐量,像数据库提供的类似于write_condition机制就是提供的乐观锁。在Java中java.util.concurrent.atomic包下面的原子变量类就是使用了乐观锁的一种实现方式CAS实现的。

4.1 悲观锁的缺点

悲观锁通过加锁的方式限制其他人对数据的操作,而乐观锁不会加锁,也就放宽了别人对数据的访问。使用悲观锁会引发一些问题:

  • 在多线程竞争下,加锁、释放锁会造成比较多的上下文切换和调度延时,引起性能问题;
  • 一个线程持有锁,会导致其他所有需要此锁的线程挂起;
  • 如果一个优先级高的线程等待一个优先级底的线程的锁,会导致优先级倒置,引起性能风险。

对比于悲观锁的这些问题,一个有效的方式就是乐观锁。其实乐观锁就是:每次不加锁而是假设没有并发冲突而去完成某项操作,如果因为并发冲突失败就重试,直到成功为止。

4.2 乐观锁的一种实现方式:CAS

CAS的全称是Compare And Swap,比较和替换。CAS操作包括三个操作数内存位置(V)、进行比较的预期原值(A)和拟写入的新值(B)。如果内存位置V的值与预期原值A相匹配,那么处理器会自动将该位置值更新为新值B。否则处理器不做任何操作。一般配合死循环来不断尝试更新值,直到成功。

相对于synchronized这种阻塞算法,CAS是一种非阻塞算法的常用实现。

CAS实现的过程是:调用java的JNI接口--> 调用c接口 --> 汇编语言调用CPU指令(关键指令:cmpxchg)

CAS的缺点

  1. ABA问题:意思是说当一个线程获取当前的值是A,此时另一个线程先将A变成B,再变成A,之前的线程继续执行,发现值没变还是A,就继续执行更新操作。这样可能会引发一些潜在问题,问题实例可以参考引用。通常各种乐观锁的实现用版本戳来对记录或者对象进行标记,来避免ABA问题,比如可以使用时间戳。JDK的atomic包里提供了一个类AtomicStampedReference来解决ABA问题。
  2. 循环时间开销大:不成功就会一直循环直到成功,如果长时间不成功会给CPU带来非常大的执行开销。如果JVM支持pause指令那么可以一定程度上减少开销。
  3. 只能保证一个共享变量的原子操作:当对一个共享变量执行操作时,我们可以使用循环CAS的方式来保证原子操作,但是对多个共享变量操作时,循环CAS就无法保证操作的原子性,这个时候就可以用锁,或者有一个取巧的办法,就是把多个共享变量合并成一个共享变量来操作。比如有两个共享变量i=2,j=a,合并一下ij=2a,然后用CAS来操作ij。从Java1.5开始JDK提供了AtomicReference类来保证引用对象之间的原子性,你可以把多个变量放在一个对象里来进行CAS操作。

CAS和synchronized的使用场景:   

  • 对于资源竞争较少(线程冲突较轻)的情况,使用synchronized同步锁进行线程阻塞和唤醒切换以及用户态内核态间的切换操作额外浪费消耗cpu资源;而CAS基于硬件实现,不需要进入内核,不需要切换线程,操作自旋几率较少,因此可以获得更高的性能。
  • 对于资源竞争严重(线程冲突严重)的情况,CAS自旋的概率会比较大,从而浪费更多的CPU资源,效率低于synchronized。

补充: synchronized在jdk1.6之后,已经改进优化。synchronized的底层实现主要依靠Lock-Free的队列,基本思路是自旋后阻塞,竞争切换后继续竞争锁,稍微牺牲了公平性,但获得了高吞吐量。在线程冲突较少的情况下,可以获得和CAS类似的性能;而线程冲突严重的情况下,性能远高于CAS。

总结一下就是:线程冲突小的情况下使用CAS,线程冲突多的情况下使用synchronized。

参考:

乐观锁和悲观锁:https://www.cnblogs.com/qjjaz...

五. 分段锁

分段锁是对锁的一种优化,就是将锁细分的粒度更多,比如将一个数组的每个位置当做单独的锁。JDK8以前ConcurrentHashMap就使用了锁分段技术,它将散列数组分成多个Segment,每个Segment存储了实际的数据,访问数据的时候只需要对数据所在的Segment加锁就行。

六. 偏向锁/轻量级锁/重量级锁 /自旋锁

重量级锁是悲观锁的一种,自旋锁,轻量级锁和偏向锁属于乐观锁。

6.1 自旋锁

在Java中,自旋锁是指尝试获取锁的线程不会立即阻塞,而是采用循环的方式去尝试获取锁,这样的好处是减少线程上下文切换的消耗,缺点是循环会消耗CPU。

自旋锁的目的是为了占着CPU的资源不释放,等到获得锁立即处理,但是如何选择自选的执行时间呢?如果自选执行的时间太长,会有大量的线程处于自旋状态占用CPU资源,进而会影响整体系统的性能,因此自旋的周期选择额外重要。

JVM对于自旋周期的选择,JDK1.5这个限度是写死的,1.6引入了适应性自旋锁,适应性自旋锁意味着自旋的时间不再固定,而是由前一次在同一个锁上的自旋时间以及锁的拥有者的状态决定,基本认为一个线程上下文切换的时间是最佳的一个时间,同时JVM还针对当前CPU的负载情况做了较多的优化:

  • 如果平均负载小于CPUs则一直自旋
  • 如果有超过(CPUs/2)个线程正在自旋,则后来线程直接阻塞
  • 如果正在自旋的线程发现Owner发生了变化则延迟自旋时间(自旋计数)或进入阻塞
  • 如果CPU处于节电模式则停止自旋
  • 自旋时间的最坏情况的CPU的存储延迟(CPU a存储了一个数据,到CPU b得知这个数据直接的时间差)
  • 自旋时会适当放弃线程优先级之间的差异

自旋锁的开启

JDK1.6中-XX:+UseSpinning开启;
-XX:PreBlockSpin=10 为自旋次数;
JDK1.7后,去掉此参数,由jvm控制;

6.2 重量级锁

重量级锁的代表就是Synchronized。但是不能单纯的说Synchronized就是重量级锁,JDK1.6对Synchronized做了优化,Synchronized锁有一个升级的过程,升级到最后才会变成重量级锁。

6.3 偏向锁

Java偏向锁是java6引入的一项多线程优化。偏向锁,顾名思义,它会偏向第一个访问锁的线程,如果在运行过程中,同步锁只有一个线程访问,不存在多线程争用的情况,则线程是不需要触发同步的,这种情况下,就会给线程加一个偏向锁,它通过消除资源无竞争情况下的同步原语,进一步提高了程序的运行性能。如果在运行过程中,遇到了其他线程抢占锁,则持有偏向锁的线程会被挂起,JVM就会消除它身上的偏向锁,将锁恢复到标准的轻量级锁。

偏向锁的实现

偏向锁的获取:

  1. 访问Mark Word中偏向锁的标志是否设置为1,锁标志位是否是01,确认为可偏向状态。
  2. 如果为可偏向状态,则测试线程id是否指向当前线程,如果是,进入步骤5,否则进入步骤3。
  3. 线程id并为指向当前线程,则通过CAS操作竞争锁。如果竞争成功,则将Mark Word中线程id设置为当前线程id,然后执行5,如果执行失败,执行4;
  4. 如果CAS获取偏向锁失败,则表示有竞争。当到达全局安全点时获得偏向锁的线程被挂起,偏向锁升级为轻量级锁,然后被阻塞在安全点的线程继续往下执行同步代码;(撤销偏向锁的时候会导致stop the world)
  5. 执行同步代码。

偏向锁的释放:

偏向锁只有遇到其他线程尝试竞争偏向锁时,持有偏向锁的线程才会释放锁,线程不会主动释放偏向锁,偏向锁的撤销,需要等待全局安全点(在这个时间点上没有字节码正在执行),它会首先暂停拥有偏向锁的线程,判断锁对象是否处于被锁定状态,撤销偏向锁后恢复到未锁定(标志位为01)或轻量级锁(标志位为00)的状态。

偏向锁的适用场景

始终只有一个线程在执行同步块,在它没有执行完释放锁之前,没有其它线程去执行同步块,在锁无竞争的情况下使用,一旦有了竞争就升级为轻量级锁,升级为轻量级锁的时候需要撤销偏向锁,撤销偏向锁的时候会导致stop the word操作;
在有锁的竞争时,偏向锁会多做很多额外操作,尤其是撤销偏向所的时候会导致进入安全点,安全点会导致stw,导致性能下降,这种情况下应当禁用。

jvm开启/关闭偏向锁

开启偏向锁:-XX:+UseBiasedLocking -XX:BiasedLockingStartupDelay=0
关闭偏向锁:-XX:-UseBiasedLocking
-XX:BiasedLockingStartupDelay=0 表示程序启动0毫秒后激活,一般jVM默认会在程序启动后4秒钟之后才激活偏向锁

6.4 轻量级锁

加锁过程

  1. 在代码进入同步块的时候,如果同步对象锁状态为无锁状态(锁标志位为01,是否为偏向锁为0),虚拟机首先将在当前线程的栈帧中建立一个名为锁记录(Lock Record)的空间,用于存储锁对象目前的Mark Word的拷贝,官方称之为Displaced Mark Word;
  2. 拷贝对象头的Mark Word到锁记录中;
  3. 拷贝成功后,虚拟机将使用CAS操作尝试将对象的Mark Word更新为指向Lock Record的指针,并将Lock Record里的owner指针指向Mark Word。如果更新成功,执行4,否则执行5;
  4. 更新成功后,那么这个线程就拥有了该对象的锁,并且对象Mark Word的标志位设置为00,即表示此对象处于轻量级锁定状态;
  5. 如果更新失败,虚拟机首先会检查对象的Mark Word是否指向当前线程的栈帧,如果是就说明当前线程已经拥有了这个对象的锁,那就直接进入同步代码块继续执行,否则说明多个线程竞争锁,轻量级锁就要膨胀为重量级锁,锁标志的状态值变为10,Mark Word中存储的就是指向重量级锁(互斥量)的指针,后面等待锁的线程也要进入阻塞状态。而当前线程便尝试使用自旋来获取锁。

6.5 总结

锁转换.png

优缺点对比

优点缺点适用场景
偏向锁加锁和解锁不需要额外的消耗,和执行非同步方法相比仅存在纳秒级的差距如果线程间存在锁竞争,会带来额外的锁撤销的消耗适用于只有一个线程访问同步块的场景
轻量级锁竞争的线程的不会阻塞,提高了程序的响应速度如果始终得不到锁竞争的线程,使用自旋会消耗CPU追求响应时间,同步块执行速度非常快
重量级锁线程竞争不使用自旋,不会消耗CPU线程阻塞,响应时间慢追求吞吐量,同步块执行速度较长
参考:

https://blog.csdn.net/zqz_zqz...

查看原文

赞 0 收藏 0 评论 0

scu酱油仔 提出了问题 · 4月1日

NIO配合多线程陷入无限循环

下面的服务器运行起来,当客户端多个请求过来的时候一直陷入死循环,selector.select();一直有值,不知道哪里出了问题,求大佬解释一下。

package com.wangjun.io.nio;

public class TimeServer {
    
    public static void main(String[] args) {
        int port = 8080;
        
        MultiplexerTimeServer timeServer = new MultiplexerTimeServer(port);
        new Thread(timeServer, "NIO_MultiplexerTimeServer-001").start();
    }

}
package com.wangjun.io.nio;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Date;
import java.util.Iterator;
import java.util.Set;

public class MultiplexerTimeServer implements Runnable {

    private Selector selector;
    private ServerSocketChannel servChannel;
    private volatile boolean stop;
    
    private final long SLEEP_TIME = 10000L;

    /**
     * 初始化多路复用器,绑定监听端口
     * 
     * @param port
     */
    public MultiplexerTimeServer(int port) {
        try {
            selector = Selector.open();
            servChannel = ServerSocketChannel.open();
            servChannel.configureBlocking(false);
            servChannel.socket().bind(new InetSocketAddress(port), 1024);//???1024这个参数什么作用
            servChannel.register(selector, SelectionKey.OP_ACCEPT);
            System.out.println("The nio time server start in port:" + port);
        } catch (IOException e) {
            e.printStackTrace();
            System.exit(1);
        }
    }

    public void stop() {
        this.stop = true;
    }

    @Override
    public void run() {
        while (!stop) {
            try {
                selector.select();
                Set<SelectionKey> selectedKeys = selector.selectedKeys();
                Iterator<SelectionKey> it = selectedKeys.iterator();
                SelectionKey key = null;
                System.out.println("key size=" + selectedKeys.size());
                while (it.hasNext()) {
                    System.out.println("it has next");
                    key = it.next();
                    it.remove();
                    try {
                        new Thread(new MyThread(key)).start();
                    } catch (Exception e) {
                        e.printStackTrace();
                        if (key != null) {
                            key.cancel();
                            if (key.channel() != null) {
                                key.channel().close();
                            }
                        }
                    }
                }
            } catch (Throwable t) {
                t.printStackTrace();
            }
        }
        //多路复用器关闭后,所有注册在上面的Channel和Pipe等资源都会被自动去注册并关闭,所有不需要重复释放资源
        if (selector != null) {
            try {
                selector.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    private void doWrite(SocketChannel channel, String response) throws IOException {
        if(response != null && response.trim().length() > 0) {
            byte[] bytes = response.getBytes();
            ByteBuffer writeBuffer = ByteBuffer.allocate(bytes.length);
            writeBuffer.put(bytes);
            writeBuffer.flip();
            channel.write(writeBuffer);
        }
    }
    
    class MyThread implements Runnable {
        
        private SelectionKey key;
        
        public MyThread(SelectionKey key) {
            this.key = key;
        }
        @Override
        public void run() {
            long startTime = System.currentTimeMillis();
            try {
                if (key.isValid()) {
                    // 处理新接入的请求消息
                    if (key.isAcceptable()) {
                        System.out.println("收到 accept 请求");
                        ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
                        SocketChannel sc = ssc.accept();
                        sc.configureBlocking(false);
                        sc.register(selector, SelectionKey.OP_READ);
                    }
                    if (key.isReadable()) {
                        System.out.println("收到 read 请求,休眠" + SLEEP_TIME/1000 + "s");
                        try {
                            Thread.sleep(SLEEP_TIME);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        SocketChannel sc = (SocketChannel) key.channel();
                        ByteBuffer readBuffer = ByteBuffer.allocate(1024);
                        int readBytes = sc.read(readBuffer);
                        if (readBytes > 0) {
                            readBuffer.flip();
                            byte[] bytes = new byte[readBuffer.remaining()];
                            readBuffer.get(bytes);
                            String body = new String(bytes, "UTF-8");
                            System.out.println("时间服务器收到的命令是:" + body);
                            String currentTime = "QUERY TIME ORDER".equalsIgnoreCase(body)
                                    ? new Date(System.currentTimeMillis()).toString()
                                    : "BAD ORDER";
                            doWrite(sc, currentTime);
                        } else if (readBytes < 0) {
                            System.out.println("对端链路关闭");
                            // 对端链路关闭
                            key.cancel();
                            sc.close();
                        } else {
                            System.out.println("收到0字节");
                            // 读到0字节,忽略
                        }
                    }
                }else {
                    System.out.println("key is not valid");
                }
            } catch (ClosedChannelException e) {
                e.printStackTrace();
            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                System.out.println("执行线程耗费:" + (System.currentTimeMillis() - startTime)/1000 + "s");
            }
        }
    }
}

客户端:

package com.wangjun.io.nio;

public class TimeClient {
    
    public static void main(String[] args) throws InterruptedException {
        int port = 8080;
        
        long start = System.currentTimeMillis();
        Thread t1 = new Thread(new TimeClientHandle("127.0.0.1", port), "TimeClient-001");
        Thread t2 = new Thread(new TimeClientHandle("127.0.0.1", port), "TimeClient-002");
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println("执行2个线程耗费:" + (System.currentTimeMillis() - start));
    }

}
package com.wangjun.io.nio;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;

public class TimeClientHandle implements Runnable {
    
    private String host;
    private int port;
    private Selector selector;
    private SocketChannel socketChannel;
    private volatile boolean stop;
    
    public TimeClientHandle(String host, int port) {
        this.host = host;
        this.port = port;
        try {
            selector = Selector.open();
            socketChannel = SocketChannel.open();
            socketChannel.configureBlocking(false);
        } catch (IOException e) {
            e.printStackTrace();
            System.exit(1);
        }
    }

    @Override
    public void run() {
        try {
            doConnect();
        } catch (IOException e) {
            e.printStackTrace();
            System.exit(1);
        }
        while(!stop) {
            try {
                selector.select();
                Set<SelectionKey> selectedKeys = selector.selectedKeys();
                Iterator<SelectionKey> it = selectedKeys.iterator();
                SelectionKey key = null;
                while (it.hasNext()) {
                    key = it.next();
                    it.remove();
                    try {
                        handleInput(key);
                    } catch (Exception e) {
                        if (key != null) {
                            key.cancel();
                            if (key.channel() != null) {
                                key.channel().close();
                            }
                        }
                    }
                }
            } catch (Throwable t) {
                t.printStackTrace();
            }
        }
        //多路复用器关闭后,所有注册在上面的Channel和Pipe等资源都会被自动去注册并关闭,所有不需要重复释放资源
        if (selector != null) {
            try {
                selector.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    
    private void handleInput(SelectionKey key) throws IOException {
        if (key.isValid()) {
            // 判断连接是否成功
            SocketChannel sc = (SocketChannel) key.channel();
            if(key.isConnectable()) {
                if(sc.finishConnect()) {
                    sc.register(selector, SelectionKey.OP_READ);
                    doWrite(sc);
                }else {
                    System.exit(1);
                }
            }

            if (key.isReadable()) {
                ByteBuffer readBuffer = ByteBuffer.allocate(1024);
                int readBytes = sc.read(readBuffer);
                if (readBytes > 0) {
                    readBuffer.flip();
                    byte[] bytes = new byte[readBuffer.remaining()];
                    readBuffer.get(bytes);
                    String body = new String(bytes, "UTF-8");
                    System.out.println("现在的时间是:" + body);
                    this.stop = true;
                } else if (readBytes < 0) {
                    // 对端链路关闭
                    key.cancel();
                    sc.close();
                } else {
                    // 读到0字节,忽略
                }
            }
        }
    }
    
    private void doConnect() throws IOException {
        //如果直连成功,则注册到多路复用器上,发送请求消息,读应答
        if(socketChannel.connect(new InetSocketAddress(host, port))) {
            socketChannel.register(selector, SelectionKey.OP_READ);
            doWrite(socketChannel);
        }else {
            socketChannel.register(selector, SelectionKey.OP_CONNECT);
        }
    }
    
    private void doWrite(SocketChannel sc) throws IOException {
        byte[] req = "QUERY TIME ORDER".getBytes();
        ByteBuffer writeBuffer = ByteBuffer.allocate(req.length);
        writeBuffer.put(req);
        writeBuffer.flip();
        sc.write(writeBuffer);
        if(!writeBuffer.hasRemaining()) {
            System.out.println("send order to server succeed!");
        }
    }
}

服务端和客户端都起来后,服务端的打印输出:

......
it has next
收到 read 请求,休眠10s
收到 read 请求,休眠10s
it has next
收到 read 请求,休眠10s
key size=2
it has next
it has next
收到 read 请求,休眠10s
收到 read 请求,休眠10s
key size=2
it has next
......

一直在打开线程,不断循环,不知道是哪里处理问题,在服务端命名已经remove掉了key。

关注 2 回答 1

scu酱油仔 发布了文章 · 3月26日

Java阻塞式IO通信

阻塞式IO通信

一、BIO通信介绍

网络编程的基本模型是Client/Server模型,也就是两个进程之间进行相互通信,其中服务端提供位置信息(绑定的IP地址和监听端口),客户端通过连接操作向服务端监听的地址发起连接请求,通过三次握手建立连接,如果连接建立成功,双方就可以通过网络套接字(Socket)进行通信。在基于传统同步阻塞模型开发中,ServerSocket负责绑定IP地址,启动监听端口;Socket负责发起连接操作。连接成功之后,双方通过输入和输出流进行同步阻塞式通信。

先看一下BIO通信模型,采用BIO通信模型的服务端,通常由一个独立的Acceptor线程负责监听客户端的连接,它接收到客户端连接请求之后为每个客户端创建一个新的线程进行链路处理,处理完成之后,通过输出流返回应答给客户端,线程销毁。这就是典型的一请求一应答通信模型。

BIO通信模型.png

该模型最大的问题就是缺乏弹性伸缩能力,当客户端并发访问量增加后,服务端的线程个数和客户端并发访问数呈1:1的正比关系,由于线程是Java虚拟机非常宝贵的系统资源,当线程数膨胀之后,系统的性能将急剧下降,随着并发访问量的继续增大,系统会发生线程堆栈溢出、创建新线程失败等问题,并最终导致进程宕机或者僵死,不能对外提供服务。

二、代码实例

2.1 服务端

TimeServer.java 主线程,监听端口的线程

package com.wangjun.io.bio;

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;

/**
 * 同步阻塞式IO创建时间服务器
 * @author wangjun
 * @date 2020-03-25
 * @version 1.0
 */
public class TimeServer {
    public static void main(String[] args) throws IOException {
        int port = 8080;
        if(args != null && args.length > 0) {
            try {
                port = Integer.valueOf(args[0]);
            } catch (NumberFormatException e) {
                //port采用默认值
            }
        }
        
        ServerSocket server = null;
        try {
            server = new ServerSocket(port);
            System.out.println("server started, port:" + port);
            Socket socket = null;
            while(true) {
                socket = server.accept();
                //启动一个新线程处理请求
                System.out.println("start a thread, hostName:" + socket.getInetAddress().getHostName());
                new Thread(new TimeServerHandler(socket)).start();
            }
        } finally {
            if(server != null) {
                System.out.println("server close");
                server.close();
                server = null;
            }
        }
    }
}

TimeServerHandler.java 子线程,处理客户端请求的线程

package com.wangjun.io.bio;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.Date;

/**
 * @author wangjun
 * @date 2020-03-25
 * @version 1.0
 */
public class TimeServerHandler implements Runnable {
    
    private Socket socket;
    public TimeServerHandler(Socket socket) {
        this.socket = socket;
    }

    @Override
    public void run() {
        BufferedReader in = null;
        PrintWriter out = null;
        try {
            in = new BufferedReader(new InputStreamReader(this.socket.getInputStream()));
            out = new PrintWriter(this.socket.getOutputStream(), true);
            String currentTime =  null;
            String body = null;
            while(true) {
                System.out.println("start read data");
                body = in.readLine();
                if(body == null) {
                    System.out.println("body is null");
                    break;
                }
                System.out.println("server receive order:" + body);
                currentTime = "QUERY TIME ORDER".equals(body)? new Date(System.currentTimeMillis()).toString(): "BAD ORDER";
                out.println(currentTime);
            }
        } catch (Exception e) {
            e.printStackTrace();
            if(in != null) {
                try {
                    in.close();
                } catch (IOException e1) {
                    e1.printStackTrace();
                }
            }
            if(out != null) {
                out.close();
                out = null;
            }
            if(this.socket != null) {
                try {
                    this.socket.close();
                } catch (IOException e1) {
                    e1.printStackTrace();
                }
                this.socket = null;
            }
        }
    }

}

启动TimeServer,可以看到打印:

server started, port:8080

此时因为没有客户端接入,主线程阻塞在socket = server.accept();通过Java VisualVM打印线程堆栈,可以看到主程序确实阻塞在accept操作上:

"main" #1 prio=5 os_prio=0 tid=0x00000000053a3000 nid=0x618 runnable [0x000000000539f000]
   java.lang.Thread.State: RUNNABLE
        at java.net.DualStackPlainSocketImpl.accept0(Native Method)
        at java.net.DualStackPlainSocketImpl.socketAccept(Unknown Source)
        at java.net.AbstractPlainSocketImpl.accept(Unknown Source)
        at java.net.PlainSocketImpl.accept(Unknown Source)
        - locked <0x000000076b6e0dc8> (a java.net.SocksSocketImpl)
        at java.net.ServerSocket.implAccept(Unknown Source)
        at java.net.ServerSocket.accept(Unknown Source)
        at com.wangjun.io.bio.TimeServer.main(TimeServer.java:30)

   Locked ownable synchronizers:
        - None

"VM Thread" os_prio=2 tid=0x000000001f109000 nid=0x39dc runnable 

2.2 客户端

TimeClient.java 客户端请求socket通信

package com.wangjun.io.bio;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;

/**
 * @author wangjun
 * @date 2020-03-25
 * @version 1.0
 */
public class TimeClient {
    public static void main(String[] args) {
        int port = 8080;
        Socket socket = null;
        BufferedReader in = null;
        PrintWriter out = null;
        try {
            socket = new Socket("127.0.0.1", port);
            in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            out = new PrintWriter(socket.getOutputStream());
            out.println("QUERY TIME ORDER");
            out.flush();
            System.out.println("send order to server succeed");
            String resp = in.readLine();
            System.out.println("Now is:" + resp);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            System.out.println("client finally");
            if(out != null) {
                out.close();
                out = null;
            }
            if(in != null) {
                try {
                    in.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
                in = null;
            }
            if(socket != null) {
                try {
                    socket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
                socket = null;
            }
        }
    }
}

启动客户端,打印:

send order to server succeed
Now is:Wed Mar 25 18:25:45 CST 2020
client finally

服务端打印:

start a thread, hostName:127.0.0.1
start read data
server receive order:QUERY TIME ORDER
start read data
body is null

这样就完成了一次socket通信。

三、遇到的问题

3.1 server阻塞

问题现象:

客户端没有拿到返回值,通过Java VisualVM打印线程堆栈发现服务端阻塞在了body = in.readLine();

这里有个疑问,既然阻塞在readLine处为什么线程的状态还是RUNNABLE呢?这是因为这里的阻塞是IO阻塞,不是线程阻塞,这是两个概念,IO阻塞一般不会造成线程阻塞,至于IO阻塞中线程会不会占用CPU应该是有系统底层的线程调度决定,比如在Linux中等待IO的过程中线程不会占用CPU,知道IO完成会唤醒线程重新抢夺CPU时间片。

参考:I/O会一直占用CPU吗?https://www.zhihu.com/questio...

"Thread-0" #11 prio=5 os_prio=0 tid=0x0000000020d50000 nid=0x21f0 runnable [0x0000000021abe000]
   java.lang.Thread.State: RUNNABLE
        at java.net.SocketInputStream.socketRead0(Native Method)
        at java.net.SocketInputStream.socketRead(Unknown Source)
        at java.net.SocketInputStream.read(Unknown Source)
        at java.net.SocketInputStream.read(Unknown Source)
        at sun.nio.cs.StreamDecoder.readBytes(Unknown Source)
        at sun.nio.cs.StreamDecoder.implRead(Unknown Source)
        at sun.nio.cs.StreamDecoder.read(Unknown Source)
        - locked <0x00000006c2230df0> (a java.io.InputStreamReader)
        at java.io.InputStreamReader.read(Unknown Source)
        at java.io.BufferedReader.fill(Unknown Source)
        at java.io.BufferedReader.readLine(Unknown Source)
        - locked <0x00000006c2230df0> (a java.io.InputStreamReader)
        at java.io.BufferedReader.readLine(Unknown Source)
        at com.wangjun.io.bio.TimeServerHandler.run(TimeServerHandler.java:33)
        at java.lang.Thread.run(Unknown Source)

   Locked ownable synchronizers:
        - None

原因分析:

后来发现是因为客户端没有进行flush操作(最开始的时候忘记写out.flush();),导致服务端阻塞在readline处。flush方法刷新输出流并强制将所有缓冲的输出字节被写出。

查看原文

赞 0 收藏 0 评论 0

scu酱油仔 发布了文章 · 3月26日

Java线程状态及其转换

线程状态及其转换

一、线程状态

Java中定义线程的状态有6种,可以查看Thread类的State枚举:

public static enum State
  {
    NEW,  RUNNABLE,  BLOCKED,  WAITING,  TIMED_WAITING,  TERMINATED;
    
    private State() {}
  }
  1. 初始(NEW):新创建了一个线程对象,还没调用start方法;
  2. 运行(RUNNABLE):java线程中将就绪(ready)和运行中(running)统称为运行(RUNNABLE)。线程创建后调用了该对象的start方法,此时处于就绪状态,当获得CPU时间片后变为运行中状态;
  3. 阻塞(BLOCKED):表现线程阻塞于锁;
  4. 等待(WAITING):进入该状态的线程需要等待其他线程做出一些特定动作(通知或中断);
  5. 超时等待(TIMED_WAITING):该状态不同于WAITING,它可以在指定时间后自行返回;
  6. 终止(TERMINATED):表示该线程已经执行完毕。

二、线程状态转换

来看一张线程状态转换图:

线程状态转换.png

下面从代码实例看线程的各个状态:

2.1 超时等待

public class Test {
    public static void main(String[] args) throws Exception {
        System.out.println("start");
        Thread.sleep(100000);
        System.out.println("end");
    }
}

通过Java VisualVM打印线程dump可以看到此线程处于TIMED_WAITING状态:

...
"main" #1 prio=5 os_prio=0 tid=0x00000000055b3800 nid=0x4e8c waiting on condition [0x000000000558f000]
   java.lang.Thread.State: TIMED_WAITING (sleeping)
        at java.lang.Thread.sleep(Native Method)
        at Test.main(Test.java:4)

   Locked ownable synchronizers:
        - None
...

2.2 等待

public class Test {
    public static void main(String[] args) throws Exception {
        Thread1 t = new Thread1();
        t.start();
        t.join();
    }
    
    static class Thread1 extends Thread {
        @Override
        public void run() {
            System.out.println("start");
            try {
                Thread.sleep(100000);
            } catch (InterruptedException e) {}
            System.out.println("end");
        }
    }
}

同样通过线程dump可以看到主线程处于WAITING状态,子线程处于TIMED_WAITING状态:

...
"Thread-0" #11 prio=5 os_prio=0 tid=0x0000000020bf7000 nid=0x4f94 waiting on condition [0x000000002189f000]
   java.lang.Thread.State: TIMED_WAITING (sleeping)
        at java.lang.Thread.sleep(Native Method)
        at Test$Thread1.run(Test.java:13)

   Locked ownable synchronizers:
        - None
...
"main" #1 prio=5 os_prio=0 tid=0x0000000004f63800 nid=0x431c in Object.wait() [0x0000000004eef000]
   java.lang.Thread.State: WAITING (on object monitor)
        at java.lang.Object.wait(Native Method)
        - waiting on <0x000000076b6e0898> (a Test$Thread1)
        at java.lang.Thread.join(Unknown Source)
        - locked <0x000000076b6e0898> (a Test$Thread1)
        at java.lang.Thread.join(Unknown Source)
        at Test.main(Test.java:5)

   Locked ownable synchronizers:
        - None
...

下面演示wait方法导致的等待状态:

public class Test {
    public static int i = 0;
    public static void main(String[] args) throws Exception {
        Thread1 t = new Thread1();
        t.start();
        synchronized (t) {
            System.out.println("等待子线程");
            t.wait();
        }
        System.out.println("主线程结束");
    }
    
    static class Thread1 extends Thread {
        @Override
        public void run() {
            synchronized (this) {
                for (int i = 0; i < 10; i++) {
                    try {
                        System.out.println(i);
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                    }
                }
                notify();
            }
        }
    }
    
}

通过线程堆栈观察,主线程同样处于等待WAITING状态:

...
"main" #1 prio=5 os_prio=0 tid=0x0000000005983800 nid=0xb54 in Object.wait() [0x00000000058df000]
   java.lang.Thread.State: WAITING (on object monitor)
        at java.lang.Object.wait(Native Method)
        - waiting on <0x000000076b6e0aa8> (a Test$Thread1)
        at java.lang.Object.wait(Unknown Source)
        at Test.main(Test.java:8)
        - locked <0x000000076b6e0aa8> (a Test$Thread1)

   Locked ownable synchronizers:
        - None
...

2.3 阻塞

public class Test {
    public static void main(String[] args) throws Exception {
        Thread1 t = new Thread1();
        t.start();
        test();
    }
    
    static class Thread1 extends Thread {
        @Override
        public void run() {
            test();
        }
    }
    
    static synchronized void test() {
        System.out.println(Thread.currentThread().getName() + " -- start");
        try {
            Thread.sleep(100000);
        } catch (InterruptedException e) {}
        System.out.println(Thread.currentThread().getName() + " -- end");
    }
}

通过线程dump可以看到子线程处于阻塞(BLOCKED)状态,主线程处于超时等待(TIMED_WAITING)状态:

...
"Thread-0" #11 prio=5 os_prio=0 tid=0x0000000020ef1800 nid=0x4df4 waiting for monitor entry [0x0000000021b9f000]
   java.lang.Thread.State: BLOCKED (on object monitor)
        at Test.test(Test.java:16)
        - waiting to lock <0x000000076b6dea88> (a java.lang.Class for Test)
        at Test$Thread1.run(Test.java:11)

   Locked ownable synchronizers:
        - None
...
"main" #1 prio=5 os_prio=0 tid=0x00000000051e3800 nid=0x3ee8 waiting on condition [0x000000000517f000]
   java.lang.Thread.State: TIMED_WAITING (sleeping)
        at java.lang.Thread.sleep(Native Method)
        at Test.test(Test.java:18)
        - locked <0x000000076b6dea88> (a java.lang.Class for Test)
        at Test.main(Test.java:5)

   Locked ownable synchronizers:
        - None
...

三、几种方法的对比

  1. Thead.sleep(long millis):一定是当前线程调用此方法,当前线程进入TIMED_WAITING状态,但不释放对象锁,millis后线程自动苏醒进入就绪状态。作用:给其它线程执行机会的最佳方式。
  2. Thread.yield():一定是当前线程调用此方法,当前线程放弃获取的CPU时间片,但不释放锁资源,由运行状态变为就绪状态,让OS再次选择线程。作用:让相同优先级的线程轮流执行,但并不保证一定会轮流执行。实际中无法保证yield()达到让步目的,因为让步的线程还有可能被线程调度程序再次选中。Thread.yield()不会导致阻塞。该方法与sleep()类似,只是不能由用户指定暂停多长时间。
  3. obj.join()/obj.join(long millis):当前线程里调用其它线程T的join方法,当前线程进入WAITING/TIMED_WAITING状态,当前线程不会释放已经持有的对象锁。线程T执行完毕或者millis时间到,当前线程一般情况下进入RUNNABLE状态,也有可能进入BLOCKED状态(因为join是基于wait实现的)。
  4. obj.wait()/obj.wait(long millis):当前线程调用对象的wait()方法,当前线程释放对象锁,进入等待队列。依靠notify()/notifyAll()唤醒或者wait(long timeout) timeout时间到自动唤醒。
  5. obj.notify():唤醒在此对象监视器上等待的单个线程,选择是任意性的。notifyAll()唤醒在此对象监视器上等待的所有线程。notify,notifyAll和wait一起使用,用于协调多个线程对共享数据的存取,所以必须在synchronized语句块内使用,也就是说,调用wait(),notify()和notifyAll()的任务在调用这些方法前必须拥有对象的锁。
  6. LockSupport.park()/LockSupport.parkNanos(long nanos),LockSupport.parkUntil(long deadlines):当前线程进入WAITING/TIMED_WAITING状态。对比wait方法,不需要获得锁就可以让线程进入WAITING/TIMED_WAITING状态,需要通过LockSupport.unpark(Thread thread)唤醒。
参考:

Java线程的6种状态及切换:https://blog.csdn.net/pange19...

查看原文

赞 0 收藏 0 评论 0

scu酱油仔 提出了问题 · 3月17日

解决java中静态方法存储在哪?

java中静态方法存储在哪,方法区还是java栈,运行时数据又存在哪,会像非静态方法一样在java栈中创建一个栈帧么?

关注 3 回答 2

scu酱油仔 发布了文章 · 3月11日

Mysql慢查询日志

Mysql慢查询日志

数据库的慢查询是影响项目性能的一大因素,对于数据库我们要优化SQL,首先要找到需要优化的SQL,这就需要我们知道sql执行时间等信息,除了使用SHOW PROFILES;外,mysql也提供了“慢查询日志”功能,用来记录查询时间超过某个设定值的SQL,这将极大程度帮助我们快速定位到症结所在,以便对症下药。

一、慢查询配置

关于慢查询日志,主要涉及三个参数:

  • slow_query_log :是否开启慢查询日志功能(必填)
  • long_query_time :超过设定值,将被视作慢查询,并记录至慢查询日志文件中(必填)
  • slow_query_log_file :慢查询日志文件(不必填),可以通过show variables like '%slow_query%';查询日志位置

打开慢查询日志有两种方式:

1. 通过命令行

不需要重启命令行,临时性的,退出mysql终端就失效。

# 以下操作管理员才有权限

mysql> set global slow_query_log = ON;
Query OK, 0 rows affected (0.04 sec)

# 设置查询“超时”时间(这里为了方便日志打印,将超过0.001s的都作为慢查询)
mysql> set GLOBAL long_query_time = 0.001;
Query OK, 0 rows affected (0.00 sec)

2. 通过配置文件

需要重启mysql

# 慢日志相关配置
slow_query_log = ON
long_query_time = 0.001
slow_query_log_file = /usr/local/mysql/data/slow.log

二、慢日志查询

如果操作正确,那么在日志里面就会看到类似下面的:

# Time: 200303 14:54:38
# User@Host: wangjun[wangjun] @ localhost []
# Thread_id: 47  Schema: scujoo  QC_hit: No
# Query_time: 0.024923  Lock_time: 0.000130  Rows_sent: 3488  Rows_examined: 3488
# Rows_affected: 0  Bytes_sent: 354872
SET timestamp=1583218478;
select * from account;
/usr/sbin/mysqld, Version: 10.3.15-MariaDB-1-log (Raspbian testing-staging). started with:
Tcp port: 0  Unix socket: /run/mysqld/mysqld.sock
Time            Id Command    Argument
# Time: 200303 15:05:30
# User@Host: [root] @ localhost []
# Thread_id: 8  Schema: mysql  QC_hit: No
# Query_time: 0.001743  Lock_time: 0.000168  Rows_sent: 1  Rows_examined: 1
# Rows_affected: 0  Bytes_sent: 252
use mysql;
SET timestamp=1583219130;
show variables like 'datadir';
# User@Host: [root] @ localhost []
# Thread_id: 10  Schema:   QC_hit: No
# Query_time: 0.007002  Lock_time: 0.000238  Rows_sent: 36  Rows_examined: 69
# Rows_affected: 0  Bytes_sent: 2391
SET timestamp=1583219130;
select concat('select count(*) into @discard from `',
                    TABLE_SCHEMA, '`.`', TABLE_NAME, '`') 
      from information_schema.TABLES where TABLE_SCHEMA<>'INFORMATION_SCHEMA' and TABLE_SCHEMA<>'PERFORMANCE_SCHEMA' and ( ENGINE='MyISAM' or ENGINE='Aria' );
# Time: 200303 15:06:41
查看原文

赞 0 收藏 0 评论 0

scu酱油仔 发布了文章 · 2019-09-20

大数据学习之路之HBASE

Hadoop之HBASE

一、HBASE简介

HBase是一个开源的、分布式的,多版本的,面向列的,半结构化的NoSql数据库,提供高性能的随机读写结构化数据的能力。它可以直接使用本地文件系统,也可以使用Hadoop的HDFS文件存储系统。不过,为了提高数据的可靠性和系统的健壮性,并且发挥HBase处理大数据的能力,使用HDFS作为文件存储系统才更为稳妥。

HBase存储的数据从逻辑上来看就像一张很大的表,并且它的数据列可以根据需要动态地增加。除此之外,每个单元(cell,由行和列所确定的位置)中的数据又可以具有多个版本(通过时间戳来区别)。从下图可以看出,HBase还具有这样的特点:它向下提供了存储,向上提供了运算。另外,在HBase之上还可以使用Hadoop的MapReduce计算模型来并行处理大规模数据,这也是它具有强大性能的核心所在。它将数据存储与并行计算完美地结合在一起。

图片描述

HBase 和 HDFS

HDFSHBase
HDFS是适于存储大容量文件的分布式文件系统。HBase是建立在HDFS之上的数据库。
HDFS不支持快速单独记录查找。HBase提供在较大的表快速查找。
它提供了高延迟批量处理;没有批处理概念。它提供了数十亿条记录低延迟访问单个行记录(随机存取)。
它提供的数据只能顺序访问。HBase内部使用哈希表和提供随机接入,并且其存储索引,可将在HDFS文件中的数据进行快速查找。

二、HBASE表结构

HBASE表具有以下特点:

  • 大:一个表可以有上亿行,上百万列
  • 面向列:面向列(族)的存储和权限控制,列(族)独立检索。
  • 稀疏:对于为空(null)的列,并不占用存储空间,因此,表可以设计的非常稀疏。

HBase以表的形式存储数据。表有行和列组成。列划分为若干个列族(row family)。下面是HBASE表的逻辑视图:

图片描述

在shell客户端展示:

> scan 'member'
ROW                                               COLUMN+CELL                                       lisi                 column=address:, timestamp=1567757931802, value=sichuan                       lisi                 column=info:, timestamp=1567757982455, value=info2 
lisi                 column=info:love, timestamp=1567758039091, value=movie                         lisi                 column=school:, timestamp=1567758005941, value=xinhua                         zhangsan             column=address:city, timestamp=1567755403595, value=beijing                   zhangsan             column=info:, timestamp=1567755827530, value=info1                             zhangsan             column=info:age, timestamp=1567756662127, value=26 
zhangsan             column=info:birthday, timestamp=1567755398376, value=1993-11-20               zhangsan             column=info:country, timestamp=1567755402535, value=china                     zhangsan             column=school:, timestamp=1567757294341, value=shiyan                         2 row(s)
Took 0.0945 seconds

下面依次介绍这些结构:

  • Row key:用来检索记录的主键,类似key-value结构的key。访问hbase table的行,只有三种方式:

    • 通过单个row key访问;
    • 通过row key的range;
    • 全表扫描;
  • 列族:hbase表中的每个列,都属于某个列族,列族属于表结构(必须在使用表之前定义),列不属于(插入数据的时候可以随时添加列),比如上面的infoaddressschool这些属于列族,info:ageinfo:love这些属于列。
  • Cell:row key和列以及时间戳唯一确定的单元,用来存储真实的数据,cell中的数据没有类型,全部是字节码形式存储。
  • 时间戳:每个cell中保存着同一份数据的多个版本,版本通过时间戳来索引。为了避免数据存在过多版本造成的的管理 (包括存贮和索引)负担,hbase提供了两种数据版本回收方式。一是保存数据的最后n个版本,二是保存最近一段时间内的版本(比如最近七天)。用户可以针对每个列族进行设置。

三、安装运行HBASE

wget http://apache.01link.hk/hbase/2.2.0/hbase-2.2.0-bin.tar.gz 
tar -zxvf hbase-2.2.0-bin.tar.gz 
cd hbase-2.2.0
vim conf/hbase-site.xml
<configuration>
    <property>
        <name>hbase.rootdir</name>
        <value>file:///tmp/hbase-${user.name}/hbase</value>
    </property>
</configuration>
# 单机模式运行,使用的是本次文件存储。不依赖Hadoop
./bin/start-hbase.sh
# 查看进程
jps
9758 HMaster
# 启动成功后可以在 http://localhost:16010 访问hbase的web页面
# 停止Hbase服务
./bin/stop-hbase.sh

# 进入HBASE shell
./bin/hbase shell
HBase Shell
Use "help" to get list of supported commands.
Use "exit" to quit this interactive shell.
For Reference, please visit: http://hbase.apache.org/2.0/book.html#shell
Version 2.2.0, rUnknown, Tue Jun 11 04:30:30 UTC 2019
Took 0.0128 seconds                                                                                                                                                                              
hbase(main):001:0>

四、shell DDL操作

# 建表
> create 'member','member_id','address','info'
Created table member
Took 1.6592 seconds                                                                                                                                                                              
=> Hbase::Table - member

# 列出所有表
> list
TABLE                                                                                                                                                                                            
member                                                                                                                                                                                           
1 row(s)
Took 0.1501 seconds                                                                                                                                                                              
=> ["member"]

# 列出表描述
> describe 'member'
Table member is ENABLED                                                                            member                                                                                            COLUMN FAMILIES DESCRIPTION                                                                         {NAME => 'address', VERSIONS => '1', EVICT_BLOCKS_ON_CLOSE => 'false', NEW_VERSION_BEHAVIOR => 'false', KEEP_DELETED_CELLS => 'FALSE', CACHE_DATA_ON_WRITE => 'false', DATA_BLOCK_ENCODING => 'NO
NE', TTL => 'FOREVER', MIN_VERSIONS => '0', REPLICATION_SCOPE => '0', BLOOMFILTER => 'ROW', CACHE_INDEX_ON_WRITE => 'false', IN_MEMORY => 'false', CACHE_BLOOMS_ON_WRITE => 'false', PREFETCH_BLO
CKS_ON_OPEN => 'false', COMPRESSION => 'NONE', BLOCKCACHE => 'true', BLOCKSIZE => '65536'}                                                                                                       

{NAME => 'info', VERSIONS => '1', EVICT_BLOCKS_ON_CLOSE => 'false', NEW_VERSION_BEHAVIOR => 'false', KEEP_DELETED_CELLS => 'FALSE', CACHE_DATA_ON_WRITE => 'false', DATA_BLOCK_ENCODING => 'NONE'
, TTL => 'FOREVER', MIN_VERSIONS => '0', REPLICATION_SCOPE => '0', BLOOMFILTER => 'ROW', CACHE_INDEX_ON_WRITE => 'false', IN_MEMORY => 'false', CACHE_BLOOMS_ON_WRITE => 'false', PREFETCH_BLOCKS
_ON_OPEN => 'false', COMPRESSION => 'NONE', BLOCKCACHE => 'true', BLOCKSIZE => '65536'}                                                                                                          

{NAME => 'member_id', VERSIONS => '1', EVICT_BLOCKS_ON_CLOSE => 'false', NEW_VERSION_BEHAVIOR => 'false', KEEP_DELETED_CELLS => 'FALSE', CACHE_DATA_ON_WRITE => 'false', DATA_BLOCK_ENCODING => '
NONE', TTL => 'FOREVER', MIN_VERSIONS => '0', REPLICATION_SCOPE => '0', BLOOMFILTER => 'ROW', CACHE_INDEX_ON_WRITE => 'false', IN_MEMORY => 'false', CACHE_BLOOMS_ON_WRITE => 'false', PREFETCH_B
LOCKS_ON_OPEN => 'false', COMPRESSION => 'NONE', BLOCKCACHE => 'true', BLOCKSIZE => '65536'}                                                                                                     

3 row(s)

QUOTAS                                                                                                                                                                                           
0 row(s)
Took 0.6478 seconds

# 删除一个列族,alter,disable,enable
> alter 'member',{NAME=>'member_id',METHOD=>'delete'}
# 在用describe 查看表会发现只有两个列族了

# 删除一个表,首先要先disable这个表
> disable 'member'
> drop 'member'

# 表是否存在
> exists 'member'

# 判断表是否enable
> is_enabled 'member'

# 判断表是否disable
> is_disabled 'member'

五、shell DML操作

# 插入数据
put'member','zhangsan','info:age','24'
put'member','zhangsan','info:birthday','1993-11-20'
put'member','zhangsan','info:country','china'
put'member','zhangsan','address:city','beijing'
put'member','lisi','info:birthday','1998-09-09'
put'member','lisi','info:favotite','movie'
put'member','lisi','address:city','beijing'

# 获取一个id的所有数据
> get'member','zhangsan'
COLUMN                                            CELL                                              address:city                                     timestamp=1567754003312, value=beijing             info:age                                         timestamp=1567753903167, value=24                 info:birthday                                    timestamp=1567753950339, value=1993-11-20         info:country                                     timestamp=1567753964169, value=china               1 row(s)
Took 0.1351 seconds
# 获取一个id,一个列族的所有数据
> get'member','zhangsan','info'
COLUMN                                            CELL                                             info:age                                         timestamp=1567753903167, value=24                 info:birthday                                    timestamp=1567753950339, value=1993-11-20         info:country                                     timestamp=1567753964169, value=china               1 row(s)
Took 0.0455 seconds
# 获取一个id,一个列族中一个列的所有数据
> get'member','zhangsan','info:age'
COLUMN                                            CELL                                             info:age                                         timestamp=1567753903167, value=24                 1 row(s)
Took 0.0364 seconds 

# 更新一条记录
> put'member','zhangsan','info:age','25'
> get'member','zhangsan','info:age'
COLUMN                                            CELL                                             info:age                                         timestamp=1567754315161, value=25                 1 row(s)
Took 0.0491 seconds
# 通过timestamp来获取指定版本的数据
> get'member','zhangsan',{COLUMN=>'info:age',TIMESTAMP=>1567753903167}
COLUMN                                            CELL                                             info:age                                         timestamp=1567753903167, value=24                 1 row(s)
Took 0.0342 seconds

# 全表扫描
> scan 'member'
ROW                                COLUMN+CELL                                                                                                                                    
  lisi                              column=address:city, timestamp=1567754078391, value=beijing       lisi                              column=info:birthday, timestamp=1567754038812, value=1998-09-09   lisi                              column=info:favotite, timestamp=1567754057750, value=movie       zhangsan                          column=address:city, timestamp=1567754003312, value=beijing       zhangsan                          column=info:age, timestamp=1567754315161, value=25               zhangsan                          column=info:birthday, timestamp=1567753950339, value=1993-11-20   zhangsan                          column=info:country, timestamp=1567753964169, value=china       2 row(s)
Took 0.1000 seconds

# 删除指定字段
> delete'member','zhangsan','info:age'
# 这个很有意思,如果有两个版本的数据,那么只会删除最新的一个版本,当再次查询的时候结果就是上一个版本的
> get'member','zhangsan','info:age'
COLUMN                                            CELL                                             info:age                                         timestamp=1567753903167, value=24                 1 row(s)
Took 0.0454 seconds
# 再次执行delete就能把当前版本删除
> delete'member','zhangsan','info:age'
> get'member','zhangsan','info:age'
COLUMN                                            CELL                                              0 row(s)
Took 0.0166 seconds

# 删除整行
> deleteall'member','lisi'
Took 0.0235 seconds

# 查询表中有多少行
> count'member'
1 row(s)
Took 0.3753 seconds                                                                                 => 1

# 给"zhangsan"这个id增加'info:age'字段,并使用counter实现递增
> incr 'member','zhangsan','info:age'
COUNTER VALUE = 1
Took 0.0948 seconds
> get 'member','zhangsan','info:age' 
COLUMN                                            CELL                                             info:age                                         timestamp=1567755056584, value=\x00\x00\x00\x00\x00\x00\x00\x01                                                             1 row(s)
Took 0.0504 seconds
> incr 'member','zhangsan','info:age'
COUNTER VALUE = 2
Took 0.0211 seconds
> get 'member','zhangsan','info:age' 
COLUMN                                            CELL                                              info:age                                         timestamp=1567755133527, value=\x00\x00\x00\x00\x00\x00\x00\x02                                                             1 row(s)
Took 0.0479 seconds
# 获取当前count的值
> get_counter'member','zhangsan','info:age'
COUNTER VALUE = 2
Took 0.0145 seconds

# 清空整张表
> truncate 'member'
Truncating 'member' table (it may take a while):
Disabling table...
Truncating table...
Took 2.1687 seconds

# 如何查看多个版本的数据,首先需要更新表结构,因为默认只保存一个版本数据,我们将保存的版本数设置为3
> alter'member',{NAME=>'info',VERSIONS=>3}
> put'member','zhangsan','info:age','26'
> scan 'member',{COLUMN=>'info:age',VERSIONS=>3}
ROW                                               COLUMN+CELL                                       zhangsan                                         column=info:age, timestamp=1567756662127, value=26  zhangsan                                         column=info:age, timestamp=1567756297089, value=25 1 row(s)
Took 0.0361 seconds 
> get 'member','zhangsan',{COLUMN=>'info',VERSIONS=>3}
COLUMN                                            CELL                                              info:                                            timestamp=1567755827530, value=info1               info:age                                         timestamp=1567756662127, value=26                 info:age                                         timestamp=1567756297089, value=25                 info:birthday                                    timestamp=1567755398376, value=1993-11-20         info:country                                     timestamp=1567755402535, value=china               1 row(s)
Took 0.0622 seconds

### 六、遇到的问题

问题1:

运行hbase shell时报错:

./bin/hbase shell
2019-09-06 11:03:21,079 WARN  [main] util.NativeCodeLoader: Unable to load native-hadoop library for your platform... using builtin-java classes where applicable
HBase Shell
Use "help" to get list of supported commands.
Use "exit" to quit this interactive shell.
For Reference, please visit: http://hbase.apache.org/2.0/book.html#shell
Version 2.2.0, rUnknown, Tue Jun 11 04:30:30 UTC 2019
Took 0.0080 seconds                                                                                                                                                                              
NotImplementedError: fstat unimplemented unsupported or native support failed to load; see http://wiki.jruby.org/Native-Libraries
  initialize at org/jruby/RubyIO.java:1013
        open at org/jruby/RubyIO.java:1154
  initialize at uri:classloader:/META-INF/jruby.home/lib/ruby/stdlib/irb/input-method.rb:141
  initialize at uri:classloader:/META-INF/jruby.home/lib/ruby/stdlib/irb/context.rb:70
  initialize at uri:classloader:/META-INF/jruby.home/lib/ruby/stdlib/irb.rb:426
  initialize at /home/wangjun/software/hbase-2.2.0/lib/ruby/irb/hirb.rb:47
       start at /home/wangjun/software/hbase-2.2.0/bin/../bin/hirb.rb:207
      <main> at /home/wangjun/software/hbase-2.2.0/bin/../bin/hirb.rb:219

解决方案:

Unable to load native-hadoop library for your platform... using builtin-java classes where applicable这个问题只需要修改conf/hbase-env.sh,加入:

export LD_LIBRARY_PATH=${hadoop_home}/lib/native:$LD_LIBRARY_PATH

${hadoop_home}为你的hadoop的安装路径。

NotImplementedError: fstat unimplemented unsupported or native support failed to load这个问题的解决方案:

sudo apt-get install jruby -y
sudo apt-get install asciidoctor -y
参考:

https://www.cnblogs.com/gaope...

https://blog.csdn.net/scutshu...

查看原文

赞 0 收藏 0 评论 0

scu酱油仔 发布了文章 · 2019-09-19

大数据学习之路之Hadoop

Hadoop介绍

一、简介

Hadoop是一个开源的分布式计算平台,用于存储大数据,并使用MapReduce来处理。Hadoop擅长于存储各种格式的庞大的数据,任意的格式甚至非结构化的处理。两个核心:

  • HDFS:Hadoop分布式文件系统(Hadoop Distributed File System),具有高容错性和伸缩性,使用java开发
  • MapReduce:Google MapReduce的开源实现,分布式编程模型使用户更方便的开发并行应用

使用Hadoop可以轻松的组织计算机资源,从而搭建自己的分布式计算平台,并且可以充分利用集群的计算 和存储能力,完成海量数据的处理。

二、Hadoop的优势

  1. 高可靠性:Hadoop按位存储和处理数据的能力具有很高的可靠性
  2. 高拓展性:Hadoop是在可用的计算机集簇间分配数据完成计算任务的,这些集簇可以拓展到数以千计的节点中
  3. 高效性:Hadoop能够在节点之间动态地移动数据,以保证各个节点的动态平衡,因此其处理速度非常快
  4. 高容错性:Hadoop能够自动保存数据的多份副本,并且能够自动将失败的任务重新分配

三、关联项目

  • Common:为Hadoop及其子项目提供支持的常用工具,主要包括FileSystem,RPC和串行化库。
  • Avro:Avro用于数据序列化的系统。提供了丰富的数据结构类型、快速可压缩的二进制格式、存储持久性数据的文件集、远程调用RPC的功能和简单的动态语言集成功能。
  • MapReduce:是一种编程模型,用于大规模数据集(大于1TB)的并行运算。
  • HDFS:分布式文件系统。
  • YRAN:分布式资源管理。
  • Chukwa:开源的数据收集系统,用于监控和分析大型分布式系统的数据。
  • Hive:一个建立在Hadoop基础之上的数据仓库,它提供了一些用于对Hadoop文件中的数据集进行数据整理、特殊查询和分析存储的工具。Hive提供一种结构化数据的机制,支持类似传统RDBMS的SQL语言的查询语言来帮助那些熟悉SQL的用户查询Hadoop中的数据,该查询语言成为Hive SQL。
  • Hbase:一个分布式的、面向列的开源数据库,适合非结构化的数据存储。主要用于需要随机访问、实时读写的大数据。
  • Pig:是一个对大型数据集进行分析、评估的平台。Pig最突出的优势是它的结构能够经受住高度并行化的检验。
  • Zookeeper:为分布式应用设计的协调服务,主要为用户提供同步、配置管理、分组和命令等服务。

四、编译安装Hadoop

因为我是用的是32位系统,官方预编译版本只有64位的,无法使用,所以得编译源代码。

根据编译文件BUILDING.txt内容,安装hadoop之前需要保证有以下工具:

Hadoop编译说明书

需要:
Unix 系统
JDK1.8
maven 3.3或更高
ProtoBuffer 2.5.0
CMake 3.1或更新(如果需要编译本地代码)
Zlib develop(如果需要编译本地代码)
openssl devel(如果编译原生hadoop-pipe,并获得最佳的HDFS加密性能)
Linux FUSE(用户空间的文件系统) 2.6或更高(如果编译fuse_dfs)
第一次编译需要网络保持连接(获取所有的maven和Hadoop需要的依赖)
Python(发布文档需要)
bats(用于shell代码测试)
Node.js / bower / Ember-cli(用于编译前端UI界面)
---------------------------------------------------------------------
获得具有所有工具的环境的最简单方法是通过Docker提供的配置。
这就需要一本最近的docker版本1.4.1或者更高的可以正常工作的版本

在Linux上,你可以运行下面的命名安装Docker
$ ./start-build-env.sh
接下来显示的提示是位于源树的已安装版本,并且已安装和配置了所有必需的测试和构建工具。
请注意,在此docker环境中,您只能从您开始的位置访问Hadoop源树。因此如果你想运行
dev-support/bin/test-patch /path/to/my.patch
那么这个patch文件必须放在hadoop源树中。

在ubuntu中清楚并安装所需的软件包:
Oracle JDK 1.8 (首选)
  $ sudo apt-get purge openjdk*
  $ sudo apt-get install software-properties-common
  $ sudo add-apt-repository ppa:webupd8team/java
  $ sudo apt-get update
  $ sudo apt-get install oracle-java8-installer
Maven
  $ sudo apt-get -y install maven
本地依赖包
  $ sudo apt-get -y install build-essential autoconf automake libtool cmake zlib1g-dev pkg-config libssl-dev
ProtocolBuffer 2.5.0 (必须)
  $ sudo apt-get -y install protobuf-compiler
  
# 1.下载源码
wget https://www.apache.org/dyn/closer.cgi/hadoop/common/hadoop-3.1.2/hadoop-3.1.2-src.tar.gz
# 2.解压
tar -zxcf hadoop-3.1.2-src.tar.gz
cd hadoop-3.1.2-src
# 3.mvn编译
mvn package -Pdist,native -DskipTests -Dtar

编译这个玩意儿断断续续用了3天时间,下面是遇到的问题总结记录一下。

问题1:

mvn package -Pdist,native -DskipTests -Dtar的时候编译失败:

[ERROR] Failed to execute goal org.codehaus.mojo:native-maven-plugin:1.0-alpha-8:javah (default) on project hadoop-common: Error running javah command: Error executing command line. Exit code:2 -> [Help 1]

解决:

vim hadoop-common-project/hadoop-common/pom.xml将javah的执行路径改为绝对路径

<javahPath>${env.JAVA_HOME}/bin/javah</javahPath>
改为
<javahPath>/usr/bin/javah</javahPath>
# 具体的路径需要对应你机器上的真实路径

问题2:

mvn package -Pdist,native -DskipTests -Dtar的时候编译失败:

[ERROR] Failed to execute goal org.apache.hadoop:hadoop-maven-plugins:3.1.2:cmake-compile (cmake-compile) on project hadoop-common: CMake failed with error code 1 -> [Help 1]
org.apache.maven.lifecycle.LifecycleExecutionException: Failed to execute goal org.apache.hadoop:hadoop-maven-plugins:3.1.2:cmake-compile (cmake-compile) on project hadoop-common: CMake failed with error code 1

解决:

cmake版本不对,安装cmake3.0版本:

# download
wget https://cmake.org/files/v3.0/cmake-3.0.0.tar.gz
tar -zxvf cmake-3.0.0.tar.gz
cd cmake-3.0.0
./configure
make
sudo apt-get install checkinstall
sudo checkinstall
sudo make install
# 建立软链接
sudo ln -s bin/* /usr/bin/ 

还是不行。使用mvn package -Pdist,native -DskipTests -Dtar -e -X打印所有日志,可以找到:

[INFO] Running cmake /home/wangjun/software/hadoop-3.1.2-src/hadoop-common-project/hadoop-common/src -DGENERATED_JAVAH=/home/wangjun/software/hadoop-3.1.2-src/hadoop-common-project/hadoop-common/target/native/javah -DJVM_ARCH_DATA_MODEL=32 -DREQUIRE_BZIP2=false -DREQUIRE_ISAL=false -DREQUIRE_OPENSSL=false -DREQUIRE_SNAPPY=false -DREQUIRE_ZSTD=false -G Unix Makefiles
[INFO] with extra environment variables {}
[WARNING] Soft-float JVM detected
[WARNING] CMake Error at /home/wangjun/software/hadoop-3.1.2-src/hadoop-common-project/hadoop-common/HadoopCommon.cmake:182 (message):
[WARNING]   Soft-float dev libraries required (e.g.  'apt-get install libc6-dev-armel'
[WARNING]   on Debian/Ubuntu)
[WARNING] Call Stack (most recent call first):
[WARNING]   CMakeLists.txt:26 (include)
[WARNING] 
[WARNING] 
[WARNING] -- Configuring incomplete, errors occurred!
[WARNING] See also "/home/wangjun/software/hadoop-3.1.2-src/hadoop-common-project/hadoop-common/target/native/CMakeFiles/CMakeOutput.log".
[WARNING] See also "/home/wangjun/software/hadoop-3.1.2-src/hadoop-common-project/hadoop-common/target/native/CMakeFiles/CMakeError.log".

查看hadoop-common-project/hadoop-common/target/native/CMakeFiles/CMakeError.log日志,看到报错:

gnu/stubs-soft.h: No such file or directory

解决方案:更改hadoop-common-project/hadoop-common/HadoopCommon.cmake,将两处-mfloat-abi=softfp改为-mfloat-abi=hard,参考:https://blog.csdn.net/wuyushe...。(最好是重新解压原始包更改完重新编译,要不然可能会出错)

这个改完又有了新问题,编译Apache Hadoop MapReduce NativeTask是报错

[WARNING] /usr/bin/ranlib libgtest.a
[WARNING] make[2]: Leaving directory '/home/wangjun/software/hadoop-3.1.2-src/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-nativetask/target/native'
[WARNING] /usr/local/bin/cmake -E cmake_progress_report /home/wangjun/software/hadoop-3.1.2-src/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-nativetask/target/native/CMakeFiles  1
[WARNING] [  7%] Built target gtest
[WARNING] make[1]: Leaving directory '/home/wangjun/software/hadoop-3.1.2-src/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-nativetask/target/native'
[WARNING] /tmp/ccpXG9td.s: Assembler messages:
[WARNING] /tmp/ccpXG9td.s:2040: Error: bad instruction `bswap r5'
[WARNING] /tmp/ccpXG9td.s:2063: Error: bad instruction `bswap r1'
[WARNING] make[2]: *** [CMakeFiles/nativetask.dir/build.make:79: CMakeFiles/nativetask.dir/main/native/src/codec/BlockCodec.cc.o] Error 1
[WARNING] make[2]: *** Waiting for unfinished jobs....
[WARNING] make[1]: *** [CMakeFiles/Makefile2:96: CMakeFiles/nativetask.dir/all] Error 2
[WARNING] make[1]: *** Waiting for unfinished jobs....
[WARNING] /tmp/ccBbS5rL.s: Assembler messages:
[WARNING] /tmp/ccBbS5rL.s:1959: Error: bad instruction `bswap r5'
[WARNING] /tmp/ccBbS5rL.s:1982: Error: bad instruction `bswap r1'
[WARNING] make[2]: *** [CMakeFiles/nativetask_static.dir/build.make:79: CMakeFiles/nativetask_static.dir/main/native/src/codec/BlockCodec.cc.o] Error 1
[WARNING] make[2]: *** Waiting for unfinished jobs....
[WARNING] /tmp/cc6DHbGO.s: Assembler messages:
[WARNING] /tmp/cc6DHbGO.s:979: Error: bad instruction `bswap r2'
[WARNING] /tmp/cc6DHbGO.s:1003: Error: bad instruction `bswap r3'
[WARNING] make[2]: *** [CMakeFiles/nativetask_static.dir/build.make:125: CMakeFiles/nativetask_static.dir/main/native/src/codec/Lz4Codec.cc.o] Error 1
[WARNING] make[1]: *** [CMakeFiles/Makefile2:131: CMakeFiles/nativetask_static.dir/all] Error 2
[WARNING] make: *** [Makefile:77: all] Error 2

看错误应该是指令问题,google一番后,找到解决方案:https://issues.apache.org/jir...

编辑primitives.h文件,根据https://issues.apache.org/jir...里面的git log修改后重新编译。

经历了3天的折磨,终于成功了!来,看看成功后的显示:

[INFO] No site descriptor found: nothing to attach.
[INFO] ------------------------------------------------------------------------
[INFO] Reactor Summary for Apache Hadoop Main 3.1.2:
[INFO] 
[INFO] Apache Hadoop Main ................................. SUCCESS [  3.532 s]
[INFO] Apache Hadoop Build Tools .......................... SUCCESS [  6.274 s]
[INFO] Apache Hadoop Project POM .......................... SUCCESS [  3.668 s]
[INFO] Apache Hadoop Annotations .......................... SUCCESS [  5.743 s]
[INFO] Apache Hadoop Assemblies ........................... SUCCESS [  1.739 s]
[INFO] Apache Hadoop Project Dist POM ..................... SUCCESS [  4.782 s]
[INFO] Apache Hadoop Maven Plugins ........................ SUCCESS [ 10.777 s]
[INFO] Apache Hadoop MiniKDC .............................. SUCCESS [  5.156 s]
[INFO] Apache Hadoop Auth ................................. SUCCESS [ 18.468 s]
[INFO] Apache Hadoop Auth Examples ........................ SUCCESS [  8.293 s]
[INFO] Apache Hadoop Common ............................... SUCCESS [03:15 min]
[INFO] Apache Hadoop NFS .................................. SUCCESS [ 14.700 s]
[INFO] Apache Hadoop KMS .................................. SUCCESS [ 15.340 s]
[INFO] Apache Hadoop Common Project ....................... SUCCESS [  0.876 s]
[INFO] Apache Hadoop HDFS Client .......................... SUCCESS [ 46.540 s]
[INFO] Apache Hadoop HDFS ................................. SUCCESS [02:34 min]
[INFO] Apache Hadoop HDFS Native Client ................... SUCCESS [ 12.125 s]
[INFO] Apache Hadoop HttpFS ............................... SUCCESS [ 20.005 s]
[INFO] Apache Hadoop HDFS-NFS ............................. SUCCESS [  8.934 s]
[INFO] Apache Hadoop HDFS-RBF ............................. SUCCESS [01:08 min]
[INFO] Apache Hadoop HDFS Project ......................... SUCCESS [  0.892 s]
[INFO] Apache Hadoop YARN ................................. SUCCESS [  0.879 s]
[INFO] Apache Hadoop YARN API ............................. SUCCESS [ 25.531 s]
[INFO] Apache Hadoop YARN Common .......................... SUCCESS [01:57 min]
[INFO] Apache Hadoop YARN Registry ........................ SUCCESS [ 14.521 s]
[INFO] Apache Hadoop YARN Server .......................... SUCCESS [  0.920 s]
[INFO] Apache Hadoop YARN Server Common ................... SUCCESS [ 23.432 s]
[INFO] Apache Hadoop YARN NodeManager ..................... SUCCESS [ 28.782 s]
[INFO] Apache Hadoop YARN Web Proxy ....................... SUCCESS [  9.515 s]
[INFO] Apache Hadoop YARN ApplicationHistoryService ....... SUCCESS [ 14.077 s]
[INFO] Apache Hadoop YARN Timeline Service ................ SUCCESS [ 12.728 s]
[INFO] Apache Hadoop YARN ResourceManager ................. SUCCESS [ 51.338 s]
[INFO] Apache Hadoop YARN Server Tests .................... SUCCESS [  8.675 s]
[INFO] Apache Hadoop YARN Client .......................... SUCCESS [ 13.937 s]
[INFO] Apache Hadoop YARN SharedCacheManager .............. SUCCESS [ 10.853 s]
[INFO] Apache Hadoop YARN Timeline Plugin Storage ......... SUCCESS [ 12.546 s]
[INFO] Apache Hadoop YARN TimelineService HBase Backend ... SUCCESS [  1.069 s]
[INFO] Apache Hadoop YARN TimelineService HBase Common .... SUCCESS [ 17.176 s]
[INFO] Apache Hadoop YARN TimelineService HBase Client .... SUCCESS [ 15.662 s]
[INFO] Apache Hadoop YARN TimelineService HBase Servers ... SUCCESS [  0.901 s]
[INFO] Apache Hadoop YARN TimelineService HBase Server 1.2  SUCCESS [ 17.512 s]
[INFO] Apache Hadoop YARN TimelineService HBase tests ..... SUCCESS [ 17.327 s]
[INFO] Apache Hadoop YARN Router .......................... SUCCESS [ 14.430 s]
[INFO] Apache Hadoop YARN Applications .................... SUCCESS [  1.990 s]
[INFO] Apache Hadoop YARN DistributedShell ................ SUCCESS [ 10.400 s]
[INFO] Apache Hadoop YARN Unmanaged Am Launcher ........... SUCCESS [  7.210 s]
[INFO] Apache Hadoop MapReduce Client ..................... SUCCESS [  2.549 s]
[INFO] Apache Hadoop MapReduce Core ....................... SUCCESS [ 38.022 s]
[INFO] Apache Hadoop MapReduce Common ..................... SUCCESS [ 35.908 s]
[INFO] Apache Hadoop MapReduce Shuffle .................... SUCCESS [ 15.180 s]
[INFO] Apache Hadoop MapReduce App ........................ SUCCESS [ 18.915 s]
[INFO] Apache Hadoop MapReduce HistoryServer .............. SUCCESS [ 15.852 s]
[INFO] Apache Hadoop MapReduce JobClient .................. SUCCESS [ 12.987 s]
[INFO] Apache Hadoop Mini-Cluster ......................... SUCCESS [ 12.106 s]
[INFO] Apache Hadoop YARN Services ........................ SUCCESS [  1.812 s]
[INFO] Apache Hadoop YARN Services Core ................... SUCCESS [  8.685 s]
[INFO] Apache Hadoop YARN Services API .................... SUCCESS [  9.236 s]
[INFO] Apache Hadoop YARN Site ............................ SUCCESS [  0.859 s]
[INFO] Apache Hadoop YARN UI .............................. SUCCESS [  0.840 s]
[INFO] Apache Hadoop YARN Project ......................... SUCCESS [ 34.971 s]
[INFO] Apache Hadoop MapReduce HistoryServer Plugins ...... SUCCESS [  7.376 s]
[INFO] Apache Hadoop MapReduce NativeTask ................. SUCCESS [02:07 min]
[INFO] Apache Hadoop MapReduce Uploader ................... SUCCESS [  9.915 s]
[INFO] Apache Hadoop MapReduce Examples ................... SUCCESS [ 14.651 s]
[INFO] Apache Hadoop MapReduce ............................ SUCCESS [ 15.959 s]
[INFO] Apache Hadoop MapReduce Streaming .................. SUCCESS [ 11.747 s]
[INFO] Apache Hadoop Distributed Copy ..................... SUCCESS [ 16.314 s]
[INFO] Apache Hadoop Archives ............................. SUCCESS [  7.115 s]
[INFO] Apache Hadoop Archive Logs ......................... SUCCESS [  8.686 s]
[INFO] Apache Hadoop Rumen ................................ SUCCESS [ 12.413 s]
[INFO] Apache Hadoop Gridmix .............................. SUCCESS [ 10.490 s]
[INFO] Apache Hadoop Data Join ............................ SUCCESS [  7.894 s]
[INFO] Apache Hadoop Extras ............................... SUCCESS [  7.098 s]
[INFO] Apache Hadoop Pipes ................................ SUCCESS [ 19.457 s]
[INFO] Apache Hadoop OpenStack support .................... SUCCESS [ 12.452 s]
[INFO] Apache Hadoop Amazon Web Services support .......... SUCCESS [04:55 min]
[INFO] Apache Hadoop Kafka Library support ................ SUCCESS [ 36.248 s]
[INFO] Apache Hadoop Azure support ........................ SUCCESS [ 43.752 s]
[INFO] Apache Hadoop Aliyun OSS support ................... SUCCESS [ 34.905 s]
[INFO] Apache Hadoop Client Aggregator .................... SUCCESS [ 17.099 s]
[INFO] Apache Hadoop Scheduler Load Simulator ............. SUCCESS [ 18.819 s]
[INFO] Apache Hadoop Resource Estimator Service ........... SUCCESS [ 29.363 s]
[INFO] Apache Hadoop Azure Data Lake support .............. SUCCESS [ 30.145 s]
[INFO] Apache Hadoop Image Generation Tool ................ SUCCESS [  8.970 s]
[INFO] Apache Hadoop Tools Dist ........................... SUCCESS [ 46.265 s]
[INFO] Apache Hadoop Tools ................................ SUCCESS [  0.883 s]
[INFO] Apache Hadoop Client API ........................... SUCCESS [08:41 min]
[INFO] Apache Hadoop Client Runtime ....................... SUCCESS [06:39 min]
[INFO] Apache Hadoop Client Packaging Invariants .......... SUCCESS [  4.040 s]
[INFO] Apache Hadoop Client Test Minicluster .............. SUCCESS [13:29 min]
[INFO] Apache Hadoop Client Packaging Invariants for Test . SUCCESS [  1.937 s]
[INFO] Apache Hadoop Client Packaging Integration Tests ... SUCCESS [  1.865 s]
[INFO] Apache Hadoop Distribution ......................... SUCCESS [01:56 min]
[INFO] Apache Hadoop Client Modules ....................... SUCCESS [  5.050 s]
[INFO] Apache Hadoop Cloud Storage ........................ SUCCESS [  6.457 s]
[INFO] Apache Hadoop Cloud Storage Project ................ SUCCESS [  0.829 s]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  01:06 h
[INFO] Finished at: 2019-09-03T14:14:45+08:00
[INFO] ------------------------------------------------------------------------

编译完成后的内容在hadoop-dist里面。感受一下为了编译这个玩意儿尝试了多少个版本:

cmake-3.0.0         
cmake-3.3.0       
hadoop-2.7.7-src.tar.gz  
hadoop-2.9.2-src         
hadoop-3.1.2-src                   
protobuf-2.5.0    
cmake-3.1.0         
hadoop-2.8.5-src     
hadoop-2.9.2-src.tar.gz  
hadoop-3.1.2-src.tar.gz  
cmake-3.1.0.tar.gz  
hadoop-2.7.7-src  
hadoop-2.8.5-src.tar.gz  
hadoop-3.1.2             
hadoop-3.1.2.tar.gz

五、启动运行hadoop

hadoop-dist/target里面的hadoop-3.1.2.tar.gz拷贝到你要安装的位置,解压。

# 进入bin目录,启动前先格式化HDFS系统
cd hadoop-3.1.2/bin
./hdfs namenode -format
......
......
2019-09-03 14:35:53,356 INFO namenode.NameNode: SHUTDOWN_MSG: 
/************************************************************
SHUTDOWN_MSG: Shutting down NameNode at raspberrypi/127.0.1.1
************************************************************/
# 启动所有服务
cd ../sbin/
./start-all.sh
WARNING: Attempting to start all Apache Hadoop daemons as wangjun in 10 seconds.
WARNING: This is not a recommended production deployment configuration.
WARNING: Use CTRL-C to abort.
Starting namenodes on [raspberrypi]
Starting datanodes
Starting secondary namenodes [raspberrypi]
Starting resourcemanager
Starting nodemanagers

访问8088端口http://localhost:8088就可以看到hadoop的管理界面了!

hadoop的web界面:

# All Applications
http://localhost:8088
# DataNode Information
http://localhost:9864
# Namenode Information
http://localhost:9870
# node
http://localhost:8042
# SecondaryNamenode information
http://localhost:9868

问题1:启动时报错:

$ ./start-all.sh 
WARNING: Attempting to start all Apache Hadoop daemons as wangjun in 10 seconds.
WARNING: This is not a recommended production deployment configuration.
WARNING: Use CTRL-C to abort.
Starting namenodes on [raspberrypi]
raspberrypi: ERROR: JAVA_HOME is not set and could not be found.
Starting datanodes
localhost: ERROR: JAVA_HOME is not set and could not be found.
Starting secondary namenodes [raspberrypi]
raspberrypi: ERROR: JAVA_HOME is not set and could not be found.
Starting resourcemanager
Starting nodemanagers
localhost: ERROR: JAVA_HOME is not set and could not be found.

解决方案:

vim ./etc/hadoop/hadoop-env.sh
# export JAVA_HOME=
改为具体的java安装路径,比如
export JAVA_HOME=/usr/lib/jvm/java-8-openjdk-armhf
查看原文

赞 0 收藏 0 评论 0

认证与成就

  • 获得 108 次点赞
  • 获得 109 枚徽章 获得 7 枚金徽章, 获得 38 枚银徽章, 获得 64 枚铜徽章

擅长技能
编辑

开源项目 & 著作
编辑

(゚∀゚ )
暂时没有

注册于 2014-09-24
个人主页被 1.4k 人浏览