zookeeper的实际使用场景及原理

一、数据发布订阅/ 配置中心

1.作用:

实现配置信息的集中式管理和数据的动态更新(比如可以实时的拿到最新的配置,当节点的配置发生变化时,通过watcher机制,客户端更新本地配置)

2.实现方式:实现配置中心有两种模式:push 、pull。

zookeeper采用的是推拉相结合的方式。 客户端向服务器端注册自己需要关注的节点。一旦节点数据发生变化,那么服务器端就会向客户端发送watcher事件通知。客户端收到通知后,主动到服务器端获取更新后的数据

3.适用情况:

a.数据量比较小

b.数据内容在运行时会发生动态变更

c.集群中的各个机器共享配置

 

二、负载均衡

各个服务注册到zookeeper的节点中,当客户端的请求过来时,根据一定的算法,将请求/数据分摊多个计算机单元上。

三、分布式锁

实现分布式锁的三种方式:

1.Redis

setNX 存在则会返回0, 不存在

2.数据库唯一索引的方式

创建一个表, 通过索引唯一的方式,加锁则insert一条数据,释放锁则delete记录

create table (id , methodname …)   methodname增加唯一索引

3.zookeeper实现

a.利用临时节点

多个client的请求向zookeeper写入一个相同的临时节点,如果能成功写入则证明获取锁成功,否则会抛出异常。

优点:当客户端的session连接过期时,临时节点会自动删除

 

b.利用临时有序节点

多个客户端向zookeeper注册一个临时有序节点,然后每个节点会向其前一个节点注册一个watcher,当前一个节点被删除时,则触发watcher事件,代表该客户端获得锁。

4.命名服务

在数据库开发中,给资源命名通常有两种方式:自增ID和UUID

但是自增ID在分布式环境下无法保证唯一,UUID虽然可以保证唯一,但是没有明确语义。

所以zookeeper中的命名服务,利用了zookeeper的如下特性:

a.节点类似于文件系统中的目录结构(唯一性,利用唯一的路径可以定位到唯一的资源)

b.可以创建顺序节点(自增性)

5.master选举

为了保证服务的高可用,所以集群使用master-slave的模式来进行同步,当master挂掉时,要从slave中选取一个master来继续工作。(可能会出现脑裂问题)

使用zookeeper解决,可用防止脑裂。

多个服务向zookeeper注册Master节点(临时),但是只有一个可以注册成功,然后其他注册失败的服务开始监听Master节点,后续如果Master节点挂掉,则其他服务又可以继续去注册Master节点。

 

注:zookeeper中的Leader选举,是利用(服务ID,ZXID(事务id))的大小关系来进行投票选取Leader的。和master选举不同。

Leader-Follower      Master-Slave

 

6.分布式队列

a.先进先出队列(利用临时有序节点)

1)通过getChildren获取指定根节点下的所有子节点,子节点就是任务

2)确定自己节点在子节点中的顺序

3)如果自己不是最小的子节点,那么监控比自己小的上一个子节点,否则处于等待

4)接收watcher通知,重复流程

 

b.Barrier模式(围栏模型)

注册一个队列,设置一个值(比如说10),当在该节点下注册的节点数达到了10之后,然后才开始执行任务。

 

Semantic Versionning命名指南

一、版本号命名规则指南

版本号的格式为X.Y.Z(又称Major.Minor.Patch),递增的规则为:
X 表示主版本号,当API 的兼容性变化时,X 需递增。
Y 表示次版本号,当增加功能时(不影响API 的兼容性),Y 需递增。
Z 表示修订号,当做Bug 修复时(不影响API 的兼容性),Z 需递增。

 

0.Y.Z 的版本号表明软件处于初始开发阶段,意味着API 可能不稳定;1.0.0 表明版本已有稳定的API。

先行版本号(Pre-release)意味该版本不稳定,可能存在兼容性问题,其格式为:X.Y.Z.[a-c][正整数],如1.0.0.a1,1.0.0.b99,1.0.0.c1000。
开发版本号常用于CI-CD,格式为X.Y.Z.dev[正整数],如1.0.1.dev4。

二、修饰词

Snapshot: 版本代表不稳定、尚处于开发中的版本
Alpha: 内部版本
Beta: 测试版
Demo: 演示版
Enhance: 增强版
Free: 自由版
Full Version: 完整版,即正式版
LTS: 长期维护版本
Release: 发行版
RC: 即将作为正式版发布
Standard: 标准版
Ultimate: 旗舰版
Upgrade: 升级版

 

三、Spring版本命名规则

1. Release 版本则代表稳定的版本
2. GA 版本则代表广泛可用的稳定版(General Availability)
3. M 版本则代表里程碑版(M 是Milestone 的意思)具有一些全新的功能或是具有里程碑意义的版本。
4. RC 版本即将作为正式版发布

java-concurrent包(JUC)

https://blog.csdn.net/u013851082/article/details/68488640

https://blog.csdn.net/wbwjx/article/details/57856045

 

Demo:

package com.buaahy.juc;

import java.util.concurrent.*;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
 * @author hy
 * @date 2018-08-06 16:51
 */
public class JUCDemo {
    public static void main(String[] args) throws InterruptedException {
        BlockingQueue<String> blockingQueue = new ArrayBlockingQueue<String>(100);
        new Thread(() -> blockingQueue.add("阻塞队列中的一个元素")).start();
        new Thread(() -> {
            try {
                System.out.println(blockingQueue.take());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();

        //闭锁测试
        CountDownLatch countDownLatch = new CountDownLatch(3);
        new Thread(() -> {
            try {
                System.out.println("线程开始等待,等待倒计时结束");
                countDownLatch.await();
                System.out.println("线程等待结束,开始运行");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();
        new Thread(() -> {
            System.out.println("倒计时开始");
            while (countDownLatch.getCount() > 0) {
                System.out.println(countDownLatch.getCount());
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                countDownLatch.countDown();
            }
            System.out.println("倒计时结束");
        }).start();


        //读写锁测试  两个线程加读锁  一个线程加写锁  发现读锁上还可以加读锁  而写锁必须等到加在上面的所有锁释放后才能获得锁
        ReadWriteLock lock = new ReentrantReadWriteLock();
        new Thread(() -> {
            System.out.println("开始加读锁1");
            lock.readLock().lock();
            System.out.println("读锁1加成功");
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            lock.readLock().unlock();
            System.out.println("读锁1释放");
        }).start();

        new Thread(() -> {
            System.out.println("开始加读锁2");
            lock.readLock().lock();
            System.out.println("读锁2加成功");
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            lock.readLock().unlock();
            System.out.println("读锁2释放");
        }).start();

        new Thread(() -> {
            System.out.println("开始加写锁");
            lock.writeLock().lock();
            System.out.println("写锁加成功");
            lock.writeLock().unlock();
            System.out.println("写锁释放成功");
        }).start();

        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        //信号量测试 只有一个许可(一个线程获得后,其他线程获取要等待) 非公平的
        Semaphore semaphore = new Semaphore(1, false);
        new Thread(() -> {
            try {
                System.out.println("线程1尝试获得许可");
                semaphore.acquire();
                System.out.println("线程1获得许可成功");
                Thread.sleep(2000);
                System.out.println("线程1开始释放许可");
                semaphore.release();
                System.out.println("线程1释放许可成功");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();

        new Thread(() -> {
            try {
                System.out.println("线程2尝试获得信号量");
                semaphore.acquire();
                System.out.println("线程2获得信号量成功");
                semaphore.release();//使用完后释放
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();

        //栅栏测试
        CyclicBarrier cyclicBarrier = new CyclicBarrier(2, new Runnable() {
            @Override
            public void run() {
                System.out.println("栅栏被推翻了,开始执行。");
            }
        });

        cyclicBarrier.isBroken();
        new Thread(() -> {
            try {
                System.out.println("栅栏线程1开始等待");
                cyclicBarrier.await();
                System.out.println("栅栏线程1等待结束,继续执行");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (BrokenBarrierException e) {
                e.printStackTrace();
            }
        }).start();
        new Thread(() -> {
            try {
                System.out.println("当前等待线程数:" + cyclicBarrier.getNumberWaiting());
                System.out.println("栅栏线程2开始等待");
                cyclicBarrier.await();
                System.out.println("栅栏线程2等待结束,继续执行");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (BrokenBarrierException e) {
                e.printStackTrace();
            }
        }).start();

        System.out.println("Finished!");


    }
}

 

Redis与Memcached

一、概述:

Memcached :是高性能分布式内存缓存服务器,本质一个key-value 数据库,但不支持数据持久化,服务器关闭后全丢失。只支持key-value结构。

Redis:将大部分数据放在内存中,支持的类型有:字符串、hash、list、set、zset。

二、区别

两者都是内存数据库,数据都在内存。不过memcache还可用于缓存其他东西,例如图片、视频等等

1.Redis数据类型更丰富;

2.Redis支持持久化,挂掉后可以通过aof文件进行恢复,而memcache不行;

3.Redis还可以用来做消息队列、数据缓存等,而memcache主要用来做SQL语句缓存、用户临时数据缓存等;

4.Redis支持数据备份,即master-slave模式的主从数据备份;

5.Redis并不是所有数据all存在内存中,当物理内存用完时,可以将一些很久没用到的value 交换到磁盘。

MySQL索引失效

一、索引失效的情况

1.如果条件中有or,即使其中有条件带索引也不会使用(这也是为什么尽量少用or的原因)。要想使用or,又想让索引生效,只能将or条件中的每个列都加上索引。

2.对于多列索引,不是使用的第一部分,则不会使用索引(最左前缀匹配)

3.like查询以%开头(如果是xxx%则会使用到索引)

4.如果列类型是字符串,那一定要在条件中将数据使用引号引用起来,否则不使用索引

5.如果mysql估计使用全表扫描要比使用索引快,则不使用索引

MySQL事务隔离级别

一、事务并发问题

1、脏读:在一个事务处理过程里读取了另一个未提交的事务中的数据。(读取了另外事务未提交的内容)

2、不可重复读:一个事务范围内多次查询却返回了不同的数据值,这是由于在查询间隔,被另一个事务修改并提交了。(读取了另外事务提交的内容)

3、幻读:事务T1将表中所有某列的数据由“1”改为“2”,这时事务T2又对这个表中插入了一行数据项,而这个数据项的数值还是为“1”并且提交给数据库。而操作事务T1的用户改结束后发现还有一条记录没有改过来,就好像发生了幻觉一样,这就叫幻读。

注:不可重复读侧重于修改,幻读侧重于新增删除。解决不可重复读的问题只需住满足条件的,解决幻读需要锁表

二、MySQL事务隔离级别

事务隔离级别 脏读 不可重复读 幻读
读未提交(read-uncommitted)
不可重复读(read-committed)
可重复读(repeatable-read)
串行化(serializable)

 

三、数据库事务四大特性:ACID

原子性、一致性、隔离性、持久性

happens-before规则和as-if-serial语义

as-if-serial语义:不管怎么进行指令重排序,程序的执行结果不能被改变。

happens-before规则:一个操作的执行结果对另外一个操作可见。

规则:

  1. 程序顺序规则:一个线程中的每个操作,happens-before于该线程中的任意后续操作。
  2. 监视器锁规则:对一个锁的解锁,happens-before于随后对这个锁的加锁。
  3. volatile变量规则:对一个volatile域的写,happens-before于任意后续对这个volatile域的读。因为 java内存模型的 happens-before原则,对volatile字段的写入操作先于读操作,即使两个线程同时修改volatile变量,get 也能拿到最新的值。
  4. 传递性:如果A happens-before B,且B happens-before C,那么A happens-before C。
  5. start()规则:如果线程A执行操作ThreadB.start()(启动线程B),那么A线程的ThreadB.start()操作happens-before于线程B中的任意操作。
  6. join()规则:如果线程A执行操作ThreadB.join()并成功返回,那么线程B中的任意操作happens-before于线程A从ThreadB.join()操作成功返回。
  7. 程序中断规则:对线程interrupted()方法的调用先行于被中断线程的代码检测到中断时间的发生。
  8. 对象finalize规则:一个对象的初始化完成(构造函数执行结束)先行于发生它的finalize()方法的开始。

 

Java线程池

一、ThreadLocalPool

Java通过Executors提供四种线程池,分别为:

  • newCachedThreadPool创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。

  • newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。

  • newScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行。

  • newSingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。

ExecutorService exec = Executors.newCachedThreadPool();

MyCallable myCallable = new MyCallable();

Future<String> future = exec.submit(myCallable);

System.out.println(future.get());

exec.shutdown();

二. 构造函数

1
2
3
4
5
6
7
publicThreadPoolExecutor(int corePoolSize,
        int maximumPoolSize,
        long keepAliveTime,
        TimeUnit unit,
        BlockingQueue<Runnable> workQueue,
        ThreadFactory threadFactory,
        RejectedExecutionHandler handler)

线程池的构造函数参数多达7个,现在我们一一来分析它们对线程池的影响。

       corePoolSize:线程池中核心线程数的最大值

       maximumPoolSize:线程池中能拥有最多线程数

       workQueue:用于缓存任务的阻塞队列

       我们现在通过向线程池添加新的任务来说明着三者之间的关系。

     (1)如果没有空闲的线程执行该任务且当前运行的线程数少于corePoolSize,则添加新的线程执行该任务。

     (2)如果没有空闲的线程执行该任务且当前的线程数等于corePoolSize同时阻塞队列未满,则将任务入队列,而不添加新的线程

     (3)如果没有空闲的线程执行该任务且阻塞队列已满同时池中的线程数小于maximumPoolSize,则创建新的线程执行任务。

     (4)如果没有空闲的线程执行该任务且阻塞队列已满同时池中的线程数等于maximumPoolSize,则根据构造函数中的handler指定的策略来拒绝新的任务。

       注意,线程池并没有标记哪个线程是核心线程,哪个是非核心线程,线程池只关心核心线程的数量。

       通俗解释,如果把线程池比作一个单位的话,corePoolSize就表示正式工,线程就可以表示一个员工。当我们向单位委派一项工作时,如果单位发现正式工还没招满,单位就会招个正式工来完成这项工作。随着我们向这个单位委派的工作增多,即使正式工全部满了,工作还是干不完,那么单位只能按照我们新委派的工作按先后顺序将它们找个地方搁置起来,这个地方就是workQueue,等正式工完成了手上的工作,就到这里来取新的任务。如果不巧,年末了,各个部门都向这个单位委派任务,导致workQueue已经没有空位置放新的任务,于是单位决定招点临时工吧(临时工:又是我!)。临时工也不是想招多少就找多少,上级部门通过这个单位的maximumPoolSize确定了你这个单位的人数的最大值,换句话说最多招maximumPoolSize–corePoolSize个临时工。当然,在线程池中,谁是正式工,谁是临时工是没有区别,完全同工同酬。

        keepAliveTime:表示空闲线程的存活时间。

        TimeUnitunit:表示keepAliveTime的单位。

        为了解释keepAliveTime的作用,我们在上述情况下做一种假设。假设线程池这个单位已经招了些临时工,但新任务没有继续增加,所以随着每个员工忙完手头的工作,都来workQueue领取新的任务(看看这个单位的员工多自觉啊)。随着各个员工齐心协力,任务越来越少,员工数没变,那么就必定有闲着没事干的员工。这样的话领导不乐意啦,但是又不能轻易fire没事干的员工,因为随时可能有新任务来,于是领导想了个办法,设定了keepAliveTime,当空闲的员工在keepAliveTime这段时间还没有找到事情干,就被辞退啦,毕竟地主家也没有余粮啊!当然辞退到corePoolSize个员工时就不再辞退了,领导也不想当光杆司令啊!

       handler:表示当workQueue已满,且池中的线程数达到maximumPoolSize时,线程池拒绝添加新任务时采取的策略。

为了解释handler的作用,我们在上述情况下做另一种假设。假设线程池这个单位招满临时工,但新任务依然继续增加,线程池从上到下,从里到外真心忙的不可开交,阻塞队列也满了,只好拒绝上级委派下来的任务。怎么拒绝是门艺术,handler一般可以采取以下四种取值。

ThreadPoolExecutor.AbortPolicy() 抛出RejectedExecutionException异常
ThreadPoolExecutor.CallerRunsPolicy() 由向线程池提交任务的线程来执行该任务
ThreadPoolExecutor.DiscardOldestPolicy() 抛弃最旧的任务(最先提交而没有得到执行的任务)
ThreadPoolExecutor.DiscardPolicy() 抛弃当前的任务

workQueue:它决定了缓存任务的排队策略。对于不同的应用场景我们可能会采取不同的排队策略,这就需要不同类型的阻塞队列,在线程池中常用的阻塞队列有以下2种:

    (1)SynchronousQueue<Runnable>:此队列中不缓存任何一个任务。向线程池提交任务时,如果没有空闲线程来运行任务,则入列操作会阻塞。当有线程来获取任务时,出列操作会唤醒执行入列操作的线程。从这个特性来看,SynchronousQueue是一个无界队列,因此当使用SynchronousQueue作为线程池的阻塞队列时,参数maximumPoolSizes没有任何作用。

    (2)LinkedBlockingQueue<Runnable>:顾名思义是用链表实现的队列,可以是有界的,也可以是无界的,但在Executors中默认使用无界的。

      threadFactory:指定创建线程的工厂

     实际上ThreadPoolExecutor类中还有很多重载的构造函数,下面这个构造函数在Executors中经常用到。

1
2
3
4
5
6
7
8
public ThreadPoolExecutor(int corePoolSize,
        int maximumPoolSize,
        long keepAliveTime,
        TimeUnit unit,
        BlockingQueue<Runnable> workQueue) {
    this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
    Executors.defaultThreadFactory(), defaultHandler);
}

注意到上述的构造方法使用Executors中的defaultThreadFactory()线程工厂和ThreadPoolExecutor中的defaultHandler抛弃策略。

      使用defaultThreadFactory创建的线程同属于相同的线程组,具有同为Thread.NORM_PRIORITY的优先级,以及名为”pool-XXX-thread-“的线程名(XXX为创建线程时顺序序号),且创建的线程都是非守护进程。

      defaultHandler缺省抛弃策是ThreadPoolExecutor.AbortPolicy()。

      除了在创建线程池时指定上述参数的值外,还可在线程池创建以后通过如下方法进行设置。

1
2
3
4
5
6
Public void allowCoreThreadTimeOut(boolean value)
Public void setKeepAliveTime(long time,TimeUnit unit)
Public void setMaximumPoolSize(int maximumPoolSize)
Public void setCorePoolSize(int corePoolSize)
Public void setThreadFactory(ThreadFactory threadFactory)
Public void setRejectedExecutionHandler(RejectedExecutionHandler handler)

三、自己实现一个线程池包括以下四个基本组成部分:
• 线程管理器(ThreadPool):用于创建并管理线程池,包括创建线程,销毁线程池,添加新任务;
• 工作线程(PoolWorker):线程池中线程,在没有任务时处于等待状态,可以循环的执行任务;
• 任务接口(Task):每个任务必须实现的接口,以供工作线程调度任务的执行,它主要规定了任务的入口,任务执行
完后的收尾工作,任务的执行状态等;
• 任务队列(TaskQueue):用于存放没有处理的任务。提供一种缓冲机制;

 

参考:https://www.cnblogs.com/nullzx/p/5184164.html

Java集合框架图

Vector和HashTale都是线程安全的,效率都不是特别高,实际使用比较少。

Collection是一个接口,所有除Map外的集合类都要 继承/实现 该接口。它提供了对集合对象进行基本操作的通用接口方法。

Collections是一个包装类,是一个工具类,它包含有各种有关集合操作的静态方法。构造方法是私有的,不能实例化。