MySQL与Oracle的区别

一、并发性

并发性是oltp数据库最重要的特性,但并发涉及到资源的获取、共享与锁定。

mysql:
mysql以表级锁为主,对资源锁定的粒度很大,如果一个session对一个表加锁时间过长,会让其他session无法更新此表中的数据。
虽然InnoDB引擎的表可以用行级锁,但这个行级锁的机制依赖于表的索引,如果表没有索引,或者sql语句没有使用索引,那么仍然使用表级锁。

oracle:
oracle使用行级锁,对资源锁定的粒度要小很多,只是锁定sql需要的资源,并且加锁是在数据库中的数据行上,不依赖与索引。所以oracle对并发性的支持要好很多。

二、一致性
oracle:
oracle支持serializable的隔离级别,可以实现最高级别的读一致性。每个session提交后其他session才能看到提交的更改。oracle通过在undo表空间中构造多版本数据块来实现读一致性,
每个session查询时,如果对应的数据块发生变化,oracle会在undo表空间中为这个session构造它查询时的旧的数据块。

mysql:
mysql没有类似oracle的构造多版本数据块的机制,只支持read commited的隔离级别。一个session读取数据时,其他session不能更改数据,但可以在表最后插入数据。
session更新数据时,要加上排它锁,其他session无法访问数据。

三、事务
oracle很早就完全支持事务。

mysql在innodb存储引擎的行级锁的情况下才支持事务。

四、数据持久性
oracle
保证提交的数据均可恢复,因为oracle把提交的sql操作线写入了在线联机日志文件中,保持到了磁盘上,
如果出现数据库或主机异常重启,重启后oracle可以考联机在线日志恢复客户提交的数据。
mysql:
默认提交sql语句,但如果更新过程中出现db或主机重启的问题,也许会丢失数据。

五、提交方式
oracle默认不自动提交,需要用户手动提交。
mysql默认是自动提交。

六、逻辑备份

oracle逻辑备份时不锁定数据,且备份的数据是一致的。

mysql逻辑备份时要锁定数据,才能保证备份的数据是一致的,影响业务正常的dml使用。

七、热备份
oracle有成熟的热备工具rman,热备时,不影响用户使用数据库。即使备份的数据库不一致,也可以在恢复时通过归档日志和联机重做日志进行一致的回复。
mysql:
myisam的引擎,用mysql自带的mysqlhostcopy热备时,需要给表加读锁,影响dml操作。
innodb的引擎,它会备份innodb的表和索引,但是不会备份.frm文件。用ibbackup备份时,会有一个日志文件记录备份期间的数据变化,因此可以不用锁表,不影响其他用户使用数据库。但此工具是收费的。
innobackup是结合ibbackup使用的一个脚本,他会协助对.frm文件的备份。

八、sql语句的扩展和灵活性
mysql对sql语句有很多非常实用而方便的扩展,比如limit功能,insert可以一次插入多行数据,select某些管理数据可以不加from。
oracle在这方面感觉更加稳重传统一些。

九、复制
oracle:既有推或拉式的传统数据复制,也有dataguard的双机或多机容灾机制,主库出现问题是,可以自动切换备库到主库,但配置管理较复杂。
mysql:复制服务器配置简单,但主库出问题时,丛库有可能丢失一定的数据。且需要手工切换丛库到主库。

十、性能诊断
oracle有各种成熟的性能诊断调优工具,能实现很多自动分析、诊断功能。比如awr、addm、sqltrace、tkproof等
mysql的诊断调优方法较少,主要有慢查询日志。

十一、权限与安全

mysql的用户与主机有关,感觉没有什么意义,另外更容易被仿冒主机及ip有可乘之机。
oracle的权限与安全概念比较传统,中规中矩。

十二、分区表和分区索引
oracle的分区表和分区索引功能很成熟,可以提高用户访问db的体验。
mysql的分区表还不太成熟稳定。

十三、管理工具
oracle有多种成熟的命令行、图形界面、web管理工具,还有很多第三方的管理工具,管理极其方便高效。
mysql管理工具较少,在linux下的管理工具的安装有时要安装额外的包(phpmyadmin, etc),有一定复杂性。

服务端提供oracle服务的实例,其是数据库的核心,用于数据库的管理,对象的管理与存储、数据的存储、查询、数据库资源的监控、监听等一些服务。
而客户端只是一个与服务端交互的工具,如sqlplus,在sqlplus里执行SQL语句传到服务端,服务端进行解析后执行SQL里的操作,并将操作结果输出到客户端。

这就完成了一个客户端与服务端交互的过程。

其他:

  1. Oracle是大型数据库而Mysql是中小型数据库,Oracle市场占有率达40%,Mysql只有20%左右,同时Mysql是开源的而Oracle价格非常高。
  2. Oracle支持大并发,大访问量,是OLTP(On-Line Transaction Processing联机事务处理系统)最好的工具。

    1
    2
    3
    OLTP,也叫联机事务处理(Online  Transaction  Processing),表示事务性非常高的系统,一般都是高可用的在线系统,
    以小的事务以及小的查询为主,评估其系统的时候,一般看其每秒执行的Transaction以及Execute SQL的数量。
    在这样的系统中,单个数据库每秒处理的Transaction往往超过几百个,或者是几千个,Select 语句的执行量每秒几千甚至几万个。
  3. 安装所用的空间差别也是很大的,Mysql安装完后才152M而Oracle有3G左右,且使用的时候Oracle占用特别大的内存空间和其他机器性能。
    4.Oracle也Mysql操作上的一些区别
    ①主键 Mysql一般使用自动增长类型,在创建表时只要指定表的主键为auto increment,插入记录时,不需要再指定该记录的主键值,Mysql将自动增长;Oracle没有自动增长类型,主键一般使用的序列,插入记录时将序列号的下一个值付给该字段即可;只是ORM框架是只要是native主键生成策略即可。
    ②单引号的处理 MYSQL里可以用双引号包起字符串,ORACLE里只可以用单引号包起字符串。在插入和修改字符串前必须做单引号的替换:把所有出现的一个单引号替换成两个单引号。
    ③翻页的SQL语句的处理 MYSQL处理翻页的SQL语句比较简单,用LIMIT 开始位置, 记录个数;ORACLE处理翻页的SQL语句就比较繁琐了。每个结果集只有一个ROWNUM字段标明它的位置, 并且只能用ROWNUM<100, 不能用ROWNUM>80
    ④ 长字符串的处理 长字符串的处理ORACLE也有它特殊的地方。INSERT和UPDATE时最大可操作的字符串长度小于等于4000个单字节, 如果要插入更长的字符串, 请考虑字段用CLOB类型,方法借用ORACLE里自带的DBMS_LOB程序包。插入修改记录前一定要做进行非空和长度判断,不能为空的字段值和超出长度字段值都应该提出警告,返回上次操作。 ⑤空字符的处理 MYSQL的非空字段也有空的内容,ORACLE里定义了非空字段就不容许有空的内容。按MYSQL的NOT NULL来定义ORACLE表结构, 导数据的时候会产生错误。因此导数据时要对空字符进行判断,如果为NULL或空字符,需要把它改成一个空格的字符串。
    ⑥字符串的模糊比较 MYSQL里用 字段名 like ‘%字符串%’,ORACLE里也可以用 字段名 like ‘%字符串%’ 但这种方法不能使用索引, 速度不快。
    ⑦Oracle实现了ANSII SQL中大部分功能,如,事务的隔离级别、传播特性等而Mysql在这方面还是比较的若

5.mysql存储引擎有好多,常用的mysiam,innodb等,而创建oracle中没有存储引擎这个概念!

java 线程

进程和线程的区别

进程:每个进程都有独立的代码和数据空间(进程上下文),进程间的切换会有较大的开销,一个进程包含1--n个线程。
线程:同一类线程共享代码和数据空间,每个线程有独立的运行栈和程序计数器(PC),线程切换开销小。
线程和进程一样分为五个阶段:创建、就绪、运行、阻塞、终止。
多进程是指操作系统能同时运行多个任务(程序)。
多线程是指在同一程序中有多个顺序流在执行。

多线程的存在其实就是“最大限度的利用cpu资源”,当某一个线程的处理不需要占用cpu而之和I/O打交道的时候,让需要占用cpu资源的其他线程有机会获得cpu资源。从根本上说,这就是说多线程编程的目的。
多线程目的: 使用多线程,可以帮助我们编写出cpu最大利用率的高效程序,使得空闲时间降到最低

在java中要想实现多线程:
1
2
3
4
继续Thread类
实现Runable接口
通过Callable创建线程
线程池来实现

ExecutorService 创建线程池

// 创建一个可缓存的线程池,调用execute将重用以前构成的线程(如果线程可用)。
// 如果没有可用的线程,则创建一个新线程并添加到池中。终止并从缓存中移出那些已有60秒钟未被使用的线程。
Executors.newCachedThreadPool();
// 创建固定数目的线程池
Executors.newFixedThreadPool(1);
// 创建一个单线程化的Executor
Executors.newSingleThreadExecutor();
// 创建一个支持定时及周期性的任务执行的线程池,多数情况下可用来替代Time类。
Executors.newScheduledThreadPool(1);

为什么不推荐通过Executors直接创建线程池

原因:
java中BlockingQueue主要有两种实现,分别是ArrayBlockingQueue和LinkedBlockingQueue。ArrayBlockingQueue是一个用数组实现的有界阻塞队列,必须设置容量。
而LinkedBlockingQueue是一个用链表实现的有界阻塞队列,容量可以选择进行设置,不设置的话,将是一个无边界的阻塞队列,最大长度为Integer.MAX_VALUE.

查看new SingleExecutor时的源码可以发现,在创建LinkedBlockingQueue时,并未指定容量。
此时,LinkedBlockingQueue就是一个无边界队列,对于一个无边界队列来说,是可以不断的向队列中加入任务的,这种情况下就有可能因为任务过多而导致内存溢出的问题。

创建线程池的正确方法:

避免使用Executors创建线程池,主要是避免使用其中的默认实现,那么我们可以自己直接调用ThreadPoolExecutor的构造函数自己创建线程池。在创建的同时,给BlockQueue指定容量就可以了。

ExecutorService executor = new ThreadPoolExecutor(10, 10,
        60L, TimeUnit.SECONDS,
        new ArrayBlockingQueue<Runnable>(10));

这种情况下,一旦提交的线程数超过当前可用线程数时,就会抛出java.util.concurrent.RejectedExecutionException,
这是因为当前线程池使用的队列是有界边界队列,队列已经满了便无法继续处理新的请求。但是异常(Exception)总比发生错误(Error)要好。

ExecutorService shutdown()、ShutdownNow()、awaitTermination()

shutdown() 停止接收新任务,原来的任务继续执行
    1、停止接收新的submit的任务;
    2、已经提交的任务(包括正在跑的和队列中等待的),会继续执行完成;
    3、等到第2步完成后,才真正停止;

ShutdownNow() 停止接收新任务,原来的任务停止执行
    1、跟 shutdown() 一样,先停止接收新submit的任务;
    2、忽略队列里等待的任务;
    3、尝试将正在执行的任务interrupt中断;
    4、返回未执行的任务列表;

awaitTermination(long timeOut, TimeUnit unit):当前线程阻塞
    当前线程阻塞,直到:
    等所有已提交的任务(包括正在跑的和队列中等待的)执行完;
    或者 等超时时间到了(timeout 和 TimeUnit设定的时间);
    或者 线程被中断,抛出InterruptedException

区别

    1、shutdown() 和 shutdownNow() 的区别
        shutdown() 只是关闭了提交通道,用submit()是无效的;而内部该怎么跑还是怎么跑,跑完再停。
        shutdownNow() 能立即停止线程池,正在跑的和正在等待的任务都停下了。
    2、shutdown() 和 awaitTermination() 的区别
        shutdown() 后,不能再提交新的任务进去;但是 awaitTermination() 后,可以继续提交。
        awaitTermination()是阻塞的,返回结果是线程池是否已停止(true/false);shutdown() 不阻塞。

总结
    1、优雅的关闭,用 shutdown()
    2、想立马关闭,并得到未执行任务列表,用shutdownNow()
    3、优雅的关闭,并允许关闭声明后新任务能提交,用 awaitTermination()
    4、关闭功能 【从强到弱】 依次是:shuntdownNow() > shutdown() > awaitTermination()

例:

1、扩展Thread类实现的多线程例子

public static void main(String[] args) {
    MyThread T1 = new MyThread("A");
    MyThread T2 = new MyThread("B");
    T1.start();
    T2.start();
}
class MyThread extends Thread {
    private String name;
    public MyThread(String name) {
        this.name = name;
    }

    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println(name+":"+i);
            try {
                sleep(1000); //休眠1秒,避免太快导致看不到同时执行
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    }
}

结果:
   A:0
   B:0
   A:1
   B:1
   A:2
   B:2
   A:3
   B:3
   A:4
   B:4

2、实现Runnable 、 ExecutorService 接口的多线程例子

    ExecutorService executor = new ThreadPoolExecutor(10, 10,
                     60L, TimeUnit.SECONDS,
                     new ArrayBlockingQueue<Runnable>(10));
    public static void main(String[] args) {
        // executor
        /*for (int i = 0; i < Integer.MAX_VALUE; i++) {
            executor.execute(new SubThread());
        }*/

        // Runnable
        executor.execute(new MyRunnable());
    }

 // 1. Runnable
class SubThread implements Runnable {
    @Override
    public void run() {
        try {
            Thread.sleep(10000);
        } catch (InterruptedException e) { //do nothing
        }
    }
}

 // 2. Runnable
class MyRunnable implements Runnable{

    public void run(){
        System.out.println("Runnable:run()....");
        int i=0;
        while(i<20){
            i++;
            for(int j=0;j<1000000;j++);
            System.out.println("i="+i);
        }
    }
}
结果:
    Runnable:run()....
    i=1
    i=2
    i=3
    i=4
    i=5
    i=6
    i=7
    i=8
    i=9
    i=10
    i=11
    i=12
    i=13
    i=14
    i=15
    i=16
    i=17
    i=18
    i=19
    i=20

3、实现Callable接口的多线程例子

    ExecutorService executor = new ThreadPoolExecutor(10, 10,
                             60L, TimeUnit.SECONDS,
                             new ArrayBlockingQueue<Runnable>(10));
    public static void main(String[] args) {
        // Callable
        Future future1 = executor.submit(new MyCallable());
        Future future2 = executor.submit(new MyCallable());
        try {
            System.out.println(future1.get());
            System.out.println(future2.get());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
        // 停止接收新任务,原来的任务继续执行
        executor.shutdown();
    }
class MyCallable implements Callable<String> {
    public String call() throws Exception {
        System.out.println("开始执行Callable");
        String[] ss={"zhangsan","lisi"};
        long[] num=new long[2];
        for(int i=0;i<1000000;i++){
            num[(int)(Math.random()*2)]++;
        }

        if(num[0]>num[1]){
            return ss[0];
        }else if(num[0]<num[1]){
            throw new Exception("弃权!");
        }else{
            return ss[1];
        }
    }
}

结果;

开始执行Callable
开始执行Callable
zhangsan
zhangsan

wait和sleep的区别

1.wait和notify方法定义在Object类中,因此会被所有的类所继承。 这些方法都是final的,即它们都是不能被重写的,不能通过子类覆写去改变它们的行为。 而sleep方法是在Thread类中是由native修饰的,本地方法。
2.当线程调用了wait()方法时,它会释放掉对象的锁。 
    Thread.sleep(),它会导致线程睡眠指定的毫秒数,但线程在睡眠的过程中是不会释放掉对象的锁的。
3.正因为wait方法会释放锁,所以调用该方法时,当前的线程必须拥有当前对象的monitor,也即lock,就是锁。
要确保调用wait()方法的时候拥有锁,即,wait()方法的调用必须放在synchronized方法或synchronized块中。
4.sleep方法时间到,线程处于临时阻塞状态或者运行状态。 wait方法如果没有被设置时间,就必须要通过notify或者notifyAll来唤醒。

notify()

notify()方法会唤醒一个等待当前对象的锁的线程。 如果多个线程在等待,它们中的一个将会选择被唤醒。
这种选择是随意的,和具体实现有关。(线程等待一个对象的锁是由于调用了wait方法中的一个)。

被唤醒的线程是不能被执行的,需要等到当前线程放弃这个对象的锁。
被唤醒的线程将和其他线程以通常的方式进行竞争,来获得对象的锁。也就是说,被唤醒的线程并没有什么优先权,也没有什么劣势,对象的下一个线程还是需要通过一般性的竞争。
且notify方法和wait一样,是需要放在synchronized方法或synchronized块中。

notifyAll()方法会唤醒在此对象监视器上等待的所有线程。线程通过调用其中一个 wait 方法,在对象的监视器上等待。
直到当前线程放弃此对象上的锁定,才能继续执行被唤醒的线程。被唤醒的线程将以常规方式与在该对象上主动同步的其他所有线程进行竞争;

尽量使用notifyAll(),notify()非常容易导致死锁。

ThreadLocal 的简单使用及实现的原理

ThreadLocal简介

ThreadLocal的实例代表了一个线程局部的变量,每条线程都只能看到自己的值,并不会意识到其它的线程中也存在该变量。

它采用采用空间来换取时间的方式,解决多线程中相同变量的访问冲突问题。

ThreadLocal是什么

ThreadLocal是一个本地线程副本变量工具类。主要用于将私有线程和该线程存放的副本对象做一个映射,各个线程之间的变量互不干扰,在高并发场景下,可以实现无状态的调用,特别适用于各个线程依赖不通的变量值完成操作的场景。

ThreadLocal的核心机制

1
2
3
每个Thread线程内部都有一个Map。
Map里面存储线程本地对象(key)和线程的变量副本(value)
但是,Thread内部的Map是由ThreadLocal维护的,由ThreadLocal负责向map获取和设置线程的变量值。

所以对于不同的线程,每次获取副本值时,别的线程并不能获取到当前线程的副本值,形成了副本的隔离,互不干扰

ThreadLocal的简单使用

下面的例子中,创建了两个线程,然后线程对各自的局部变量进行递增的操作。每个线程中的局部变量的初始值都是100。

public class ThreadLocalTest {
    // ThreadLocal中的值
    static class Bank {
        ThreadLocal<Integer> t = new ThreadLocal<Integer>() {
            // 重写里面的方法就可以修改初始值了
            @Override
            protected Integer initialValue(){
                return 100;
            }
        };
        public int get() {
            return t.get();
        }
        public void set() {
            // 获取值
            t.set(t.get() + 10);
        }
        public void set(Integer value) {
            // 获取值
            t.set(value);
        }
    }
    /** 定义对ThreadLocal的操作,也就是在原来的基础上进行加10的操作,然后打印出结果。 */
    // 对ThreadLocal的操作
    static class Transfer implements Runnable {
        Bank bank;
        public Transfer(Bank bank) {
            this.bank = bank;
        }
        @Override
        public void run() {
            for (int i = 0; i < 10; i++) {
                //
                bank.set();
                // 输出不同线程的ThreadLocal的值
                System.out.println(Thread.currentThread() + ":::" + bank.get());
            }
        }
    }
    /** 创建两个线程并启动,主线程等待这两个线程执行完成。最值得注意的就是主线程中输出的bank.get(),输出的初始值100。 */
    // 创建线程并等待线程执行完成
    public static void main(String[] args) throws InterruptedException {
        //
        Bank bank = new Bank();
        // bank.set(50);
        // 多个线程都是同时操作一个变量,但是不同线程的结果是互不影响的
        Transfer t = new Transfer(bank);
        Thread t1 = new Thread(t);
        t1.start();
        Thread t2 = new Thread(t);
        t2.start();
        t1.join();
        t2.join();
        // 需要注意的是,这个是main() 线程中的变量,输出的是 ThreadLocal<Integer> t 的初始值, 也就是100
        System.out.println(bank.get());
    }
}

ThreadLocal的实现原理

ThreadLocal类提供如下几个核心方法:

public T get() { }
public void set(T value) { }
public void remove() { }
protected T initialValue() { }
1
2
3
4
get()方法用于获取当前线程的副本变量值。
set()方法用于保存当前线程的副本变量值。
initialValue()为当前线程初始副本变量值。
remove()方法移除当前前程的副本变量值

每个Thread的对象都有一个ThreadLocalMap,当创建一个ThreadLocal的时候,就会将该ThreadLocal对象添加到该Map中,其中键就是ThreadLocal,值可以是任意类型。

在该类中,重要的方法就是两个:set()和get()方法。当调用ThreadLocal的get()方法的时候,会先找到当前线程的ThreadLocalMap,然后再找到对应的值。set()方法也是一样。

public T get() {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
    return setInitialValue();
}

public void set(T value) {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
}

Spring Cloud构建微服务架构 高可用

一、SpringCloud简介

Spring Cloud是一系列框架的有序集合。
它利用Spring Boot的开发便利性巧妙地简化了分布式系统基础设施的开发,如服务发现注册、配置中心、消息总线、负载均衡、断路器、数据监控等,都可以用Spring Boot的开发风格做到一键启动和部署。
Spring Cloud并没有重复制造轮子,它只是将目前各家公司开发的比较成熟、经得起实际考验的服务框架组合起来,通过Spring Boot风格进行再封装屏蔽掉了复杂的配置和实现原理,最终给开发者留出了一套简单易懂、易部署和易维护的分布式系统开发工具包。

二、微服务的注册与发现-Eureka

Eureka是Netflix开源的服务发现组件,本身是基于Rest的服务,它包含服务端和客户端两部分;在SpringCloud中将它集成在其中,从而实现了微服务的发现与注册;

Eureka Server可以运行多个实例来构建集群,解决单点问题,Eureka Server采用的是Peer to Peer对等通信。这是一种去中心化的架构,无master/slave区分,每一个Peer都是对等的。在这种架构中,节点通过彼此互相注册来提高可用性,每个节点需要添加一个或多个有效的serviceUrl指向其他节点。每个节点都可被视为其他节点的副本。

如果某台Eureka Server宕机,Eureka Client的请求会自动切换到新的Eureka Server节点,当宕机的服务器重新恢复后,Eureka会再次将其纳入到服务器集群管理之中。当节点开始接受客户端请求时,所有的操作都会进行replicateToPeer(节点间复制)操作,将请求复制到其他Eureka Server当前所知的所有节点中。

简单来说,Eureka Server的高可用,实际上就是将自己也作为服务向其他服务注册中心进行注册,这样就可以形成一组相互注册的服务注册中心,以实现服务清单的互相同步,达到高可用的效果。

集群模式下,Eureka的架构图:
image
Eureka Server

- 服务端-没有存储,内存保持,每服务实例需要发送心跳去续约 
- 客户端-在内存中缓存着eureka的注册信息,因此不必每请求到eureka查找服务 
- eureka之间会做注册服务同步,从而保证状态一致,客户端只需访问一个

eureka Service Provider

- 会向Eureka Server做Register(服务注册)、Renew(服务续约)、Cancel(服务下线)等操作 

Service Consumer

- 会向Eureka Server获取注册服务列表,并消费服务

三、Eureka的高可用

eureka 项目下复制application.properties
分别命名为:

application-server1.properties, application-server2.properties, application-server3.properties

修改hosts文件:

127.0.0.1    localhost server1 server2 server3

application-server1.properties的配置文件:

server.port=1001
spring.application.name=eureka-server-1
spring.profiles.active=server1
#表示是否将自己注册在EurekaServer上,默认为true。由于当前应用就是EurekaServer,所以置为false
eureka.client.register-with-eureka=true
#表示表示是否从EurekaServer获取注册信息,默认为true。单节点不需要同步其他的EurekaServer节点的数据
#eureka.client.fetch-registry=false
#指定主机名
eureka.instance.hostname=server1
eureka.client.serviceUrl.defaultZone=http://server2:1002/eureka/,http://server3:1003/eureka/

application-server2.properties的配置文件:

server.port=1002
spring.application.name=eureka-server-2
spring.profiles.active=server2
#表示是否将自己注册在EurekaServer上,默认为true。由于当前应用就是EurekaServer,所以置为false
eureka.client.register-with-eureka=true
#表示表示是否从EurekaServer获取注册信息,默认为true。单节点不需要同步其他的EurekaServer节点的数据
#eureka.client.fetch-registry=false
#指定主机名
eureka.instance.hostname=server2
eureka.client.serviceUrl.defaultZone=http://server1:1001/eureka/,http://server3:1003/eureka/

application-server3.properties的配置文件:

server.port=1003
spring.application.name=eureka-server-3
spring.profiles.active=server3
#表示是否将自己注册在EurekaServer上,默认为true。由于当前应用就是EurekaServer,所以置为false
eureka.client.register-with-eureka=true
#表示表示是否从EurekaServer获取注册信息,默认为true。单节点不需要同步其他的EurekaServer节点的数据
#eureka.client.fetch-registry=false
#指定主机名
eureka.instance.hostname=server3
eureka.client.serviceUrl.defaultZone=http://server1:1001/eureka/,http://server2:1002/eureka/

compute-service 项目下复制application.properties 为: application-dev.properties
配置信息:

spring.application.name=compute-service
server.port=2001
eureka.client.serviceUrl.defaultZone=http://server3:1003/eureka/,http://server1:1001/eureka/,http://server2:1002/eureka/

分别启动服务端server3、server2、server1、compute-service

# 分别在项目下 执行 mvn clean install  命令编译 生成 target
 mvn clean install

# 在 /SpringCloudDemo/eureka/target 下执行,启动

java -jar eureka-0.0.1-SNAPSHOT.jar --spring.profiles.active=server1
java -jar eureka-0.0.1-SNAPSHOT.jar --spring.profiles.active=server2
java -jar eureka-0.0.1-SNAPSHOT.jar --spring.profiles.active=server3

# 在 /SpringCloudDemo/compute-service/target 下执行,启动
java -jar compute-service-0.0.1-SNAPSHOT.jar --spring.profiles.active=dev

访问,http://localhost:1001/http://localhost:1002/http://localhost:1003/,如下:
image
image
image

该工程可参见:SpringCloudDemo/eureka

至此,完成了Eureka的高可用;

Spring Cloud构建微服务架构 局部限流:针对某个服务进行限流

bootstrap.yml

# 局部限流:针对某个服务进行限流
##开启限流
zuul:
    # 定义的路由规则
    routes:
    export-service:
        path:  /main/api/
        url: exportPdf
        serviceId: export-service
    ratelimit:
    enabled: true
#      ## 3s 内请求超过 1 次,服务端就抛出异常,3s 后可以恢复正常请求
#      ##针对某个 url 进行限流,不影响其他 
    policy-list:
        - export-service:
        - limit: 1 #3秒内只能刷新一次
            refresh-interval: 3 #限制时间(秒) 3秒内只能刷新一次
            type:
#              - origin
            - url
#      policies:
#        export-service:
#          - limit: 1
#            refresh-interval: 10
#            type:
#  #            - user
#              - origin
#  #            - url

Spring Cloud构建微服务架构 (三) 断路器

在微服务架构中,我们将系统拆分成了一个个的服务单元,各单元间通过服务注册与订阅的方式互相依赖。由于每个单元都在不同的进程中运行,依赖通过远程调用的方式执行,这样就有可能因为网络原因或是依赖服务自身问题出现调用故障或延迟,而这些问题会直接导致调用方的对外服务也出现延迟,若此时调用方的请求不断增加,最后就会出现因等待出现故障的依赖方响应而形成任务积压,最终导致自身服务的瘫痪。

举个例子,在一个电商网站中,我们可能会将系统拆分成,用户、订单、库存、积分、评论等一系列的服务单元。用户创建一个订单的时候,在调用订单服务创建订单的时候,会向库存服务来请求出货(判断是否有足够库存来出货)。此时若库存服务因网络原因无法被访问到,导致创建订单服务的线程进入等待库存申请服务的响应,在漫长的等待之后用户会因为请求库存失败而得到创建订单失败的结果。如果在高并发情况之下,因这些等待线程在等待库存服务的响应而未能释放,使得后续到来的创建订单请求被阻塞,最终导致订单服务也不可用。

在微服务架构中,存在着那么多的服务单元,若一个单元出现故障,就会因依赖关系形成故障蔓延,最终导致整个系统的瘫痪,这样的架构相较传统架构就更加的不稳定。为了解决这样的问题,因此产生了断路器模式。
什么是断路器

断路器模式源于Martin Fowler的Circuit Breaker一文。“断路器”本身是一种开关装置,用于在电路上保护线路过载,当线路中有电器发生短路时,“断路器”能够及时的切断故障电路,防止发生过载、发热、甚至起火等严重后果。

在分布式架构中,断路器模式的作用也是类似的,当某个服务单元发生故障(类似用电器发生短路)之后,通过断路器的故障监控(类似熔断保险丝),向调用方返回一个错误响应,而不是长时间的等待。这样就不会使得线程因调用故障服务被长时间占用不释放,避免了故障在分布式系统中的蔓延。
Netflix Hystrix

在Spring Cloud中使用了Hystrix 来实现断路器的功能。Hystrix是Netflix开源的微服务框架套件之一,该框架目标在于通过控制那些访问远程系统、服务和第三方库的节点,从而对延迟和故障提供更强大的容错能力。Hystrix具备拥有回退机制和断路器功能的线程和信号隔离,请求缓存和请求打包,以及监控和配置等功能。

下面我们来看看如何使用Hystrix。
准备工作

在开始加入断路器之前,我们先拿之前构建两个微服务为基础进行下面的操作,主要使用下面几个工程:

SpringCloudDemo
    eureka工程:服务注册中心,端口1000
    compute-service工程:服务单元,端口2001 / 2000
    eureka-ribbon:通过ribbon实现的服务单元,依赖compute-service的服务,端口2002
    eureka-feign:通过feign实现的服务单元,依赖compute-service的服务,端口2003

若您还没有使用Spring Cloud的经验,可以先阅读《服务注册与发现》与《服务消费者》,对Spring Cloud构建的微服务有一个初步的认识。

Ribbon中引入Hystrix

依次启动eureka-server、compute-service、eureka-ribbon工程
访问http://localhost:1000/可以看到注册中心的状态
访问http://localhost:2002/add,调用eureka-ribbon的服务,该服务会去调用compute-service的服务,计算出10+20的值,页面显示30
关闭compute-service服务,访问http://localhost:2002/add,我们获得了下面的报错信息

{"timestamp":"2019-04-16T07:11:04.064+0000","status":500,"error":"Internal Server Error","message":"I/O error on GET request for \"http://COMPUTE-SERVICE/add\": Connection refused: connect; nested exception is java.net.ConnectException: Connection refused: connect","path":"/add"}

pom.xml中引入依赖hystrix依赖

<dependency>
    <groupId>com.netflix.hystrix</groupId>
    <artifactId>hystrix-javanica</artifactId>
    <version>RELEASE</version>
</dependency>

在eureka-ribbon的主类RibbonApplication中使用@EnableCircuitBreaker注解开启断路器功能:

@SpringBootApplication
@EnableDiscoveryClient
@EnableCircuitBreaker
public class RibbonApplication {

    @Bean
    @LoadBalanced
    RestTemplate restTemplate() {
        return new RestTemplate();
    }

    public static void main(String[] args) {
        SpringApplication.run(RibbonApplication.class, args);
    }

}

改造原来的服务消费方式,新增ComputeService类,在使用ribbon消费服务的函数上增加@HystrixCommand注解来指定回调方法。

@Service
public class ComputeService {

    @Autowired
    RestTemplate restTemplate;

    @HystrixCommand(fallbackMethod = "addServiceFallback")
    public String addService() {
        return restTemplate.getForEntity("http://COMPUTE-SERVICE/add?a=10&b=20", String.class).getBody();
    }

    public String addServiceFallback() {
        return "error";
    }

}

提供rest接口的Controller改为调用ComputeService的addService

@RestController
public class ConsumerController {

    @Autowired
    private ComputeService computeService;

    @RequestMapping(value = "/add", method = RequestMethod.GET)
    public String add() {
        return computeService.addService();
    }

}

验证断路器的回调

依次启动eureka、compute-service、eureka-ribbon工程
访问http://localhost:1000/可以看到注册中心的状态
访问http://localhost:2002/add,页面显示:30
关闭compute-service服务后再访问http://localhost:2002/add
显示:error

Feign使用Hystrix

注意这里说的是“使用”,没有错,我们不需要在Feigh工程中引入Hystix,Feign中已经依赖了Hystrix,我们可以在未做任何改造前,尝试下面你的操作:

依次启动eureka、compute-service、eureka-feign工程
访问http://localhost:1000/可以看到注册中心的状态
访问http://localhost:2003/add,调用eureka-feign的服务,该服务会去调用compute-service的服务,计算出10+20的值,页面显示30
关闭compute-service服务,访问http://localhost:2003/add,我们获得了下面的报错信息

{"timestamp":"2019-04-16T07:11:04.064+0000","status":500,"error":"Internal Server Error","message":"I/O error on GET request for \"http://COMPUTE-SERVICE/add\": Connection refused: connect; nested exception is java.net.ConnectException: Connection refused: connect","path":"/add"}

appliction.yml 配置

feign:
    hystrix:
        enabled: true

使用@FeignClient注解中的fallback属性指定回调类

@FeignClient(value = "compute-service", fallback = ComputeClientHystrix.class)
public interface ComputeClient {

    @RequestMapping(method = RequestMethod.GET, value = "/add")
    Integer add(@RequestParam(value = "a") Integer a, @RequestParam(value = "b") Integer b);

}

创建回调类ComputeClientHystrix,实现@FeignClient的接口,此时实现的方法就是对应@FeignClient接口中映射的fallback函数。

@Component
public class ComputeClientHystrix implements ComputeClient {

    @Override
    public Integer add(@RequestParam(value = "a") Integer a, @RequestParam(value = "b") Integer b) {
        return -9999;
    }

}

再用之前的方法验证一下,是否在compute-service服务不可用的情况下,页面返回了-9999。

Spring Cloud构建微服务架构(二)服务消费者

在上一篇《Spring Cloud构建微服务架构(一)服务注册与发现》中,我们已经成功创建了“服务注册中心”,实现并注册了一个“服务提供者:COMPUTE-SERVICE”。那么我们要如何去消费服务提供者的接口内容呢?

Ribbon

Ribbon是一个基于HTTP和TCP客户端的负载均衡器。Feign中也使用Ribbon,后续会介绍Feign的使用。

Ribbon可以在通过客户端中配置的ribbonServerList服务端列表去轮询访问以达到均衡负载的作用。

当Ribbon与Eureka联合使用时,ribbonServerList会被DiscoveryEnabledNIWSServerList重写,扩展成从Eureka注册中心中获取服务端列表。同时它也会用NIWSDiscoveryPing来取代IPing,它将职责委托给Eureka来确定服务端是否已经启动。

下面我们通过实例看看如何使用Ribbon来调用服务,并实现客户端的均衡负载。
准备工作

启动服务注册中心:eureka
启动服务提供方:compute-service
修改compute-service中的server-port为2000,再启动一个服务提供方:compute-service

此时访问:http://localhost:1000/

可以看到COMPUTE-SERVICE服务有两个单元正在运行:

zangqisong-PC:compute-service:2001
zangqisong-PC:compute-service:2000

image
使用Ribbon实现客户端负载均衡的消费者

构建一个基本Spring Boot项目,并在pom.xml中加入如下内容:

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.1.4.RELEASE</version>
    <relativePath/> <!-- lookup parent from repository -->
</parent>

<dependencies>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
    </dependency>
    <dependency>
        <groupId>com.netflix.hystrix</groupId>
        <artifactId>hystrix-javanica</artifactId>
        <version>RELEASE</version>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-web</artifactId>
    </dependency>
</dependencies>

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-dependencies</artifactId>
            <version>${spring-cloud.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

在应用主类中,通过@EnableDiscoveryClient注解来添加发现服务能力。创建RestTemplate实例,并通过@LoadBalanced注解开启均衡负载能力。

@SpringBootApplication
@EnableDiscoveryClient
@EnableCircuitBreaker
public class EurekaRibbonApplication {

    @Bean
    @LoadBalanced
    RestTemplate restTemplate() {
        return new RestTemplate();
    }

    public static void main(String[] args) {
        SpringApplication.run(EurekaRibbonApplication.class, args);
    }

}

创建ConsumerController来消费COMPUTE-SERVICE的add服务。通过直接RestTemplate来调用服务,计算10 + 20的值。

@RestController
public class ConsumerController {

    @Autowired
    RestTemplate restTemplate;

    @RequestMapping(value = "/add", method = RequestMethod.GET)
    public String add() {
        return restTemplate.getForEntity("http://COMPUTE-SERVICE/add?a=10&b=20", String.class).getBody();
    }

}

application.properties中配置eureka服务注册中心

spring.application.name=ribbon-consumer
server.port=2002
eureka.client.serviceUrl.defaultZone=http://localhost:1000/eureka/

启动该应用,并访问两次:http://localhost:2002/add

然后,打开compute-service的两个服务提供方,分别输出了类似下面的日志内容:

端口为2000服务提供端的日志:

2019-04-16 13:48:40.690  INFO 79592 --- [nio-2000-exec-2] c.e.c.controller.ComputeController       : /add, host:zangqisong-PC, service_id:COMPUTE-SERVICE:2000, result:30

端口为2001服务提供端的日志:

2019-04-16 13:48:40.811  INFO 114792 --- [nio-2001-exec-7] c.e.c.controller.ComputeController       : /add, host:zangqisong-PC, service_id:COMPUTE-SERVICE:2001, result:30

可以看到,之前启动的两个compute-service服务端分别被调用了一次。到这里,我们已经通过Ribbon在客户端已经实现了对服务调用的均衡负载。

该工程可参见:SpringCloudDemo/eureka-ribbon

Feign

Feign是一个声明式的Web Service客户端,它使得编写Web Serivce客户端变得更加简单。我们只需要使用Feign来创建一个接口并用注解来配置它既可完成。它具备可插拔的注解支持,包括Feign注解和JAX-RS注解。Feign也支持可插拔的编码器和解码器。Spring Cloud为Feign增加了对Spring MVC注解的支持,还整合了Ribbon和Eureka来提供均衡负载的HTTP客户端实现。

下面,通过一个例子来展现Feign如何方便的声明对上述computer-service服务的定义和调用。

创建一个Spring Boot工程,配置pom.xml,将上述的配置中的ribbon依赖替换成feign的依赖即可,具体如下:

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.1.4.RELEASE</version>
    <relativePath/> <!-- lookup parent from repository -->
</parent>

<dependencies>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-openfeign</artifactId>
    </dependency>
    <dependency>
        <groupId>com.netflix.hystrix</groupId>
        <artifactId>hystrix-javanica</artifactId>
        <version>RELEASE</version>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

在应用主类中通过@EnableFeignClients注解开启Feign功能,具体如下:

@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
@EnableAutoConfiguration(exclude={DataSourceAutoConfiguration.class})
public class EurekaFeignApplication {

    public static void main(String[] args) {
        SpringApplication.run(EurekaFeignApplication.class, args);
    }

}

定义compute-service服务的接口,具体如下:

@FeignClient("compute-service")
public interface ComputeClient {

    @RequestMapping(method = RequestMethod.GET, value = "/add")
    Integer add(@RequestParam(value = "a") Integer a, @RequestParam(value = "b") Integer b);

}

使用@FeignClient("compute-service")注解来绑定该接口对应compute-service服务
通过Spring MVC的注解来配置compute-service服务下的具体实现。

在web层中调用上面定义的ComputeClient,具体如下:

@RestController
public class ConsumerController {

    @Autowired
    ComputeClient computeClient;

    @RequestMapping(value = "/add", method = RequestMethod.GET)
    public Integer add() {
        return computeClient.add(10, 20);
    }

}

application.properties中不用变,指定eureka服务注册中心即可,如:

spring.application.name=feign-consumer
server.port=2003
eureka.client.serviceUrl.defaultZone=http://localhost:1000/eureka/

启动该应用,访问几次:http://localhost:2003/add

再观察日志,可以得到之前使用Ribbon时一样的结果,对服务提供方实现了均衡负载。

2019-04-16 14:48:21.475  INFO 114792 --- [nio-2001-exec-9] c.e.c.controller.ComputeController       : /add, host:zangqisong-PC, service_id:COMPUTE-SERVICE:2000, result:30
2019-04-16 14:48:21.741  INFO 79592 --- [nio-2000-exec-4] c.e.c.controller.ComputeController       : /add, host:zangqisong-PC, service_id:COMPUTE-SERVICE:2000, result:30

这一节我们通过Feign以接口和注解配置的方式,轻松实现了对compute-service服务的绑定,这样我们就可以在本地应用中像本地服务一下的调用它,并且做到了客户端均衡负载。

示例可参见:SpringCloudDemo/eureka-feign

Spring Cloud构建微服务架构(一)服务注册与发现

Spring Cloud 简介

Spring Cloud是一个基于Spring Boot实现的云应用开发工具,它为基于JVM的云应用开发中的配置管理、服务发现、断路器、智能路由、微代理、控制总线、全局锁、决策竞选、分布式会话和集群状态管理等操作提供了一种简单的开发方式。

Spring Cloud包含了多个子项目(针对分布式系统中涉及的多个不同开源产品),比如:Spring Cloud Config、Spring Cloud Netflix、Spring Cloud CloudFoundry、Spring Cloud AWS、Spring Cloud Security、Spring Cloud Commons、Spring Cloud Zookeeper、Spring Cloud CLI等项目。

微服务架构

“微服务架构”在这几年非常的火热,以至于关于微服务架构相关的产品社区也变得越来越活跃(比如:netflix、dubbo),Spring Cloud也因Spring社区的强大知名度和影响力也被广大架构师与开发者备受关注。

那么什么是“微服务架构”呢?简单的说,微服务架构就是将一个完整的应用从数据存储开始垂直拆分成多个不同的服务,每个服务都能独立部署、独立维护、独立扩展,服务与服务间通过诸如RESTful API的方式互相调用。

对于“微服务架构”,大家在互联网可以搜索到很多相关的介绍和研究文章来进行学习和了解。也可以阅读始祖Martin Fowler的《Microservices》,本文不做更多的介绍和描述。

服务注册与发现

在简单介绍了Spring Cloud和微服务架构之后,下面回归本文的主旨内容,如何使用Spring Cloud搭建服务注册与发现模块。

这里我们会用到Spring Cloud Netflix,该项目是Spring Cloud的子项目之一,主要内容是对Netflix公司一系列开源产品的包装,它为Spring Boot应用提供了自配置的Netflix OSS整合。通过一些简单的注解,开发者就可以快速的在应用中配置一下常用模块并构建庞大的分布式系统。它主要提供的模块包括:服务发现(Eureka),断路器(Hystrix),智能路有(Zuul),客户端负载均衡(Ribbon)等。

所以,我们这里的核心内容就是服务发现模块:Eureka。下面我们动手来做一些尝试。

创建“服务注册中心”

创建一个基础的Spring Boot工程,并在pom.xml中引入需要的依赖内容:

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.1.4.RELEASE</version>
    <relativePath/> <!-- lookup parent from repository -->
</parent>

<dependencies>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-dependencies</artifactId>
            <version>${spring-cloud.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
        </plugin>
    </plugins>
</build>

通过@EnableEurekaServer注解启动一个服务注册中心提供给其他应用进行对话。这一步非常的简单,只需要在一个普通的Spring Boot应用中添加这个注解就能开启此功能,比如下面的例子:

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;

@EnableEurekaServer
@SpringBootApplication
public class EurekaApplication {

    public static void main(String[] args) {
        SpringApplication.run(EurekaApplication.class, args);
    }

}

在默认设置下,该服务注册中心也会将自己作为客户端来尝试注册它自己,所以我们需要禁用它的客户端注册行为,只需要在application.properties中问增加如下配置:

server.port=1000
eureka.client.register-with-eureka=false
eureka.client.fetch-registry=false
eureka.client.serviceUrl.defaultZone=http://localhost:${server.port}/eureka/

为了与后续要进行注册的服务区分,这里将服务注册中心的端口通过server.port属性设置为1000。

启动工程后,访问:http://localhost:1000/

可以看到下面的页面,其中还没有发现任何服务
image
该工程可参见:SpringCloudDemo/eureka

创建“服务提供方”

下面我们创建提供服务的客户端,并向服务注册中心注册自己。

假设我们有一个提供计算功能的微服务模块,我们实现一个RESTful API,通过传入两个参数a和b,最后返回a + b的结果。

首先,创建一个基本的Spring Boot应用 compute-service,在pom.xml中,加入如下配置:

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.1.4.RELEASE</version>
    <relativePath/> <!-- lookup parent from repository -->
</parent>

<dependencies>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-dependencies</artifactId>
            <version>${spring-cloud.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

其次,实现/add请求处理接口,通过DiscoveryClient对象,在日志中打印出服务实例的相关内容。

@RestController
public class ComputeController {
    private final Logger logger = LoggerFactory.getLogger(getClass());
    // 服务注册
    @Autowired
    private Registration registration;
    @Autowired
    private DiscoveryClient client;

    @RequestMapping(value = "/add" ,method = RequestMethod.GET)
    public Integer add(@RequestParam Integer a, @RequestParam Integer b) {

        ServiceInstance instance = serviceInstance();
        Integer r = a + b;
        logger.info("/add, host:" + instance.getHost() + ", service_id:" + instance.getServiceId() + ", result:" + r);
        return r;
    }
    public ServiceInstance serviceInstance() {
        List<ServiceInstance> list = client.getInstances(registration.getServiceId());
        if (list != null && list.size() > 0) {
            for (ServiceInstance itm : list){
                if (itm.getPort() == 2001)
                    return itm;
            }
        }
        return null;
    }
}

最后在主类中通过加上@EnableDiscoveryClient注解,该注解能激活Eureka中的DiscoveryClient实现,才能实现Controller中对服务信息的输出。

@EnableDiscoveryClient
@SpringBootApplication
public class ComputeServiceApplication {

    public static void main(String[] args) {
        SpringApplication.run(ComputeServiceApplication.class, args);
    }

}

我们在完成了服务内容的实现之后,再继续对application.properties做一些配置工作,具体如下:

spring.application.name=compute-service
server.port=2001
eureka.client.serviceUrl.defaultZone=http://localhost:1000/eureka/

通过spring.application.name属性,我们可以指定微服务的名称后续在调用的时候只需要使用该名称就可以进行服务的访问。

eureka.client.serviceUrl.defaultZone属性对应服务注册中心的配置内容,指定服务注册中心的位置。

为了在本机上测试区分服务提供方和服务注册中心,使用server.port属性设置不同的端口。

启动该工程后,再次访问:http://localhost:1000/

可以看到,我们定义的服务被注册了。
image
该工程可参见:SpringCloudDemo/compute-service

node 爬虫

准备工作

Node.js环境搭建

创建工程

选择一个目录,新建一个准备存放工程内容的文件夹demo。

打开终端(windows机器打开CMD命令行),输入npm init,根据提示,逐步输入工程信息,具体示例如下

$ npm init
This utility will walk you through creating a package.json file.
It only covers the most common items, and tries to guess sensible defaults.

See `npm help json` for definitive documentation on these fields
and exactly what they do.

Use `npm install <pkg>` afterwards to install a package and
save it as a dependency in the package.json file.

Press ^C at any time to quit.
package name: (demo) demo
version: (1.0.0)
description: 爬虫
entry point: (index.js)
test command:
git repository:
keywords:
author: huapisong
license: (ISC)
About to write to D:\test\demo\package.json:

{
  "name": "demo",
  "version": "1.0.0",
  "description": "爬虫",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "huapisong",
  "license": "ISC"
}


Is this ok? (yes) yes

此时文件夹下生成了一个package.json文件,其中包含了工程的基本信息以及引用的框架等信息

框架引入

superagent:发起http请求
cheerio:解析http返回的html内容
async:多线程并发控制

安装命令 npm install –save PACKAGE_NAME,执行以下三条命令后,工程目录下多了一个node_modules目录,该目录就是引入的框架内容。

$npm install --save superagent
$npm install --save cheerio
$npm install --save async

编码过程

工程目录下,创建index.js

var superagent = require('superagent');
var cheerio = require('cheerio');
var async = require('async');

var fs = require('fs');
var request = require("request");

console.log('爬虫程序开始运行......');

// 第一步,发起请求,获取的列表
superagent
    .get('http://www.wanfangdata.com.cn/keywords/getNewKeywords.do?keywords=java&resource=all&topNumber=18')
    .set('Accept', 'application/json, text/javascript, */*; q=0.01')
    .set('Content-Type','text/html;charset=UTF-8')
    .end(function(err, res){
        // res.body, res.headers, res.status
        //let body = cheerio.load(res.body);
        // 请求返回后的处理
        // 将response中返回的结果转换成JSON对象
        var words = JSON.parse(res.text).word;
        console.log('抓取的数据:' + JSON.stringify(words));

        async.mapSeries(words,
            function (word, callback) {
                console.log('抓取的数据:' + word);
                // 对每个对象的处理逻辑
                fetchInfo(word, callback);
                console.timeEnd("  耗时");

            },
            function (err, result) {
                console.log('final:\n' + result);
            }
        );
        // 并发遍历 words对象
        /* var words = JSON.parse(res.text);
        async.mapLimit(words, 10,
            function (word, callback) {
                console.log('抓取的数据:' + word);
                // 对每个对象的处理逻辑
                fetchInfo(word, callback);
                console.timeEnd("  耗时");

            },
            function (err, result) {
                console.log('final:\n' + result);
            }
        );*/

    });

var concurrencyCount = 0; // 当前并发数记录
var fetchInfo = function(word, callback){
    console.time('  耗时');
    concurrencyCount++;
    console.log('并发数:', concurrencyCount, ',正在抓取的是', word);

    // 写入文件内容(如果文件不存在会创建一个文件)
    fs.writeFile('./try4.txt', word+'\n', { 'flag': 'a' }, function(err) {
        if (err) {
            console.log('并发数:', concurrencyCount--, 'word', word);
            callback(null, word);
            throw err;
        }
        callback(null, word);
        // 写入成功后读取测试
        /*fs.readFile('./try4.txt', 'utf-8', function(err, data) {
            if (err) {
                throw err;
            }
            console.log(data);
        });*/
    });

}

工程目录下执行命令,node index.js,抓取程序开始执行

 node index.js
爬虫程序开始运行......
抓取的数据:["Java","JAVA","java","Java语言","JAVA语言","Java技术","JAVA技术","Java3D","JavaEE","Java虚拟机","Java EE","JavaBean","Java程序设计","JavaScript","JAVASCRIPT","Javascript","Java Applet"]
抓取的数据:Java
并发数: 1 ,正在抓取的是 Java
 耗时: 1.329ms
抓取的数据:JAVA
并发数: 2 ,正在抓取的是 JAVA
 耗时: 0.646ms
抓取的数据:java
并发数: 3 ,正在抓取的是 java
 耗时: 0.625ms
抓取的数据:Java语言
并发数: 4 ,正在抓取的是 Java语言
 耗时: 0.642ms
抓取的数据:JAVA语言
并发数: 5 ,正在抓取的是 JAVA语言
 耗时: 0.663ms
抓取的数据:Java技术
并发数: 6 ,正在抓取的是 Java技术
 耗时: 0.410ms
抓取的数据:JAVA技术
并发数: 7 ,正在抓取的是 JAVA技术
 耗时: 0.395ms
抓取的数据:Java3D
并发数: 8 ,正在抓取的是 Java3D
 耗时: 0.377ms
抓取的数据:JavaEE
并发数: 9 ,正在抓取的是 JavaEE
 耗时: 0.240ms
抓取的数据:Java虚拟机
并发数: 10 ,正在抓取的是 Java虚拟机
 耗时: 0.346ms
抓取的数据:Java EE
并发数: 11 ,正在抓取的是 Java EE
 耗时: 0.306ms
抓取的数据:JavaBean
并发数: 12 ,正在抓取的是 JavaBean
 耗时: 0.323ms
抓取的数据:Java程序设计
并发数: 13 ,正在抓取的是 Java程序设计
 耗时: 0.326ms
抓取的数据:JavaScript
并发数: 14 ,正在抓取的是 JavaScript
 耗时: 0.306ms
抓取的数据:JAVASCRIPT
并发数: 15 ,正在抓取的是 JAVASCRIPT
 耗时: 0.346ms
抓取的数据:Javascript
并发数: 16 ,正在抓取的是 Javascript
 耗时: 1.027ms
抓取的数据:Java Applet
并发数: 17 ,正在抓取的是 Java Applet
 耗时: 0.955ms
final:
Java,JAVA,java,Java语言,JAVA语言,Java技术,JAVA技术,Java3D,JavaEE,Java虚拟机,Java EE,JavaBean,Java程序设计,JavaScript,JAVASCRIPT,Javascript,Java Applet

几个能够免费生成二维码的api接口

1.百度网盘(可使用https)

http://pan.baidu.com/share/qrcode?w=150&h=150&url=内容

2.iClick接口 (无https)

http://bshare.optimix.asia/barCode?site=weixin&url=内容

3.JiaThis 接口(无https)

http://s.jiathis.com/qrcode.php?url=内容

4.联图网(无https)

http://qr.liantu.com/api.php?text=内容

5.K780数据网(支持https和http)

http://api.k780.com:88/?app=qr.get&data=内容&level=L&size=6

https://sapi.k780.com/?app=qr.get&data=内容&level=L&size=6

6.QR Code Generator(https接口)

https://api.qrserver.com/v1/create-qr-code/?size=150x150&data=内容

vue elementUi

dialog 遮盖层

Vue开发中出现对话框被遮罩层挡住问题解决方案

造成这个问题的原因是对话框组件的父元素的position有fixed或者relative值,比较简单易行的办法如下:

# modal-append-to-body 遮罩层是否插入至 body 元素上,若为 false,则遮罩层会插入至 Dialog 的父元素上 boolean — true

Element UI该组件已经在属性层面提供了解决办法,只要添加
:modal-append-to-body="false"
就可以了。

vue 手机输入框回车页面刷新

当Form组件中只有一个Input组件时,鼠标聚焦输入框后,点击回车键,页面就会刷新

# 例如以下代码
<el-form :model="form">
  <el-form-item prop="username">
    <el-input v-model="form.username" placeholder="请输入姓名"></el-input>
  </el-form-item>
</el-form>

解决方法

# 方法一:
# 用@submit.native.prevent来阻止默认行为,在组件Form上添加@submit.native.prevent
<el-form :model="form"  @submit.native.prevent>
  <el-form-item prop="username">
    <el-input v-model="form.username" placeholder="请输入姓名"></el-input>
  </el-form-item>
</el-form>

# 方法二:
# 再在表单里添加一个Input组件(若需求需要一个input,可将第二个Input组件隐藏)
<el-form :model="form">
  <el-form-item prop="username">
    <el-input v-model="form.username" placeholder="请输入姓名"></el-input>
    <el-input v-show="false"></el-input>
  </el-form-item>
</el-form>

数据更新但是页面没有更新的情况

使用this.$forceUpdate()可以重新渲染组件,这样就可以得到想要的效果。
vue官方对$forceUpdate的解释是:
$forceUpdate可以迫使 Vue 实例重新渲染。它仅仅影响实例本身和插入插槽内容的子组件,而不是所有子组件。