2019年7月13日 星期六

Eureka 集群 ( SpringCloud 2.x 二)

要做集群,在本機要訪問網址不方便,所以在 hosts 新增如下的網址
Windows 的 C:\Windows\system32\drivers\etc 或 unix 的 /etc
127.0.0.1       xxx.ooo9051
127.0.0.1       xxx.ooo9052
127.0.0.1       xxx.ooo9053


※新增兩個 model,並複製 pom 裡的 spring-cloud-starter-netflix-eureka-server,然後修改三個 application.yml,以9052為例

server:
  port: 9052
    
eureka:
  instance:
    hostname: xxx.ooo9052
  client:
    register-with-eureka: false     #不註冊自己
    fetch-registry: false     #取得註冊資訊,因為自己就是註冊中心,不需要取得註冊資訊
    service-url:
      defaultZone: http://xxx.ooo9051:9051/eureka,http://xxx.ooo9053:9053/eureka


※9051 內容寫 9052 和 9053;9052 內容寫 9051 和 9053

※自己的寫在 eureka.instance.hostname


eureka:
  client:
    service-url:
      #defaultZone: http://localhost:9051/eureka
      defaultZone: http://xxx.ooo9051:9051/eureka,http://xxx.ooo9052:9052/eureka,http://xxx.ooo9053:9053/eureka


※provider 和 consumer 的 defaultZone 也得改

※結果畫面如下,以 xxx.ooo9052:9052 為例
※可以看見 DS Replicas 還有兩個 eureka,內容有 consumer1 和 provider1

2019年7月12日 星期五

向註冊中心註冊與發現服務 ( SpringCloud 2.x 一)

※POM

※父POM

<packaging>pom</packaging>
<modules>
    <module>provider</module>
    <module>consumer</module>
    <module>api</module>
    <module>eureka</module>
</modules>
    
<groupId>scp</groupId>
<artifactId>SpringCloudPractice</artifactId>
<version>1.0-SNAPSHOT</version>
    
<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.1.5.RELEASE</version>
    <relativePath/> <!-- lookup parent from repository -->
</parent>
    
<properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>1.8</maven.compiler.source>
    <maven.compiler.target>1.8</maven.compiler.target>
    <spring-cloud-release.version>Greenwich.RELEASE</spring-cloud-release.version>
</properties>
    
<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-dependencies</artifactId>
            <version>${spring-cloud-release.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-dependencies</artifactId>
            <version>2.1.5.RELEASE</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.8</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>
</dependencyManagement>
    
<dependencies>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-config</artifactId>
    </dependency>
    
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>
    
<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
        </plugin>
    </plugins>
</build>


※生產者和消費者向註冊中心註冊,api 為生產者和消費者共用的方法


※consumer pom

<parent>
    <artifactId>SpringCloudPractice</artifactId>
    <groupId>scp</groupId>
    <version>1.0-SNAPSHOT</version>
</parent>
    
<artifactId>consumer</artifactId>
    
<properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>1.8</maven.compiler.source>
    <maven.compiler.target>1.8</maven.compiler.target>
</properties>
    
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    
    <dependency>
        <artifactId>api</artifactId>
        <groupId>scp</groupId>
        <version>1.0-SNAPSHOT</version>
    </dependency>
    
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>
</dependencies>



※provider pom

<parent>
    <artifactId>SpringCloudPractice</artifactId>
    <groupId>scp</groupId>
    <version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
    
<artifactId>provider</artifactId>
    
<properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>1.8</maven.compiler.source>
    <maven.compiler.target>1.8</maven.compiler.target>
</properties>
    
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    
    <dependency>
        <artifactId>api</artifactId>
        <groupId>scp</groupId>
        <version>1.0-SNAPSHOT</version>
    </dependency>
    
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>
</dependencies>



※api pom

<parent>
    <artifactId>SpringCloudPractice</artifactId>
    <groupId>scp</groupId>
    <version>1.0-SNAPSHOT</version>
</parent>
    
<artifactId>api</artifactId>
    
<properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>1.8</maven.compiler.source>
    <maven.compiler.target>1.8</maven.compiler.target>
</properties>
    
<dependencies>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
    </dependency>
</dependencies>
    
<build>
    <finalName>cyc</finalName>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
            <configuration>
                <classifier>exec</classifier>
            </configuration>
        </plugin>
    </plugins>
</build>

※因為 api 有程式碼給 consumer 和 provider 使用,maven install 會失敗,所以要在 api 加上 plugin 才可以

※eureka pom

<parent>
    <artifactId>SpringCloudPractice</artifactId>
    <groupId>scp</groupId>
    <version>1.0-SNAPSHOT</version>
</parent>
    
<artifactId>eureka</artifactId>
    
<properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>1.8</maven.compiler.source>
    <maven.compiler.target>1.8</maven.compiler.target>
</properties>
    
<dependencies>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
        <!--<artifactId>spring-cloud-netflix-eureka-server</artifactId>没有 starter 是错的-->
    </dependency>
</dependencies>




※java 類別


※provider

package controller;
    
@RestController
public class TestController {
    @GetMapping("/testGet")
    public ApiBean get() {
        System.out.println("xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx");
        ApiBean ab = new ApiBean();
        ab.setId(1);
        ab.setName("xxx");
        return ab;
    }
}



※consumer

package controller;
    
@RestController
@Import(RestTemplate.class)
public class consumerController {
    @Resource
    private RestTemplate restTemplate;
    
    @GetMapping("/xxx")
    public ApiBean get() {
        return restTemplate.getForObject("http://localhost:9001" + "/testGet", ApiBean.class);
    }
}


※因為 RestTemplate 是內鍵的 class,並沒有類似 @Component 這種註解,spring 不知道,所以用了 @Import 將它註冊到 spring

※api

package scp;
    
@Data
public class ApiBean implements Serializable {
    private int id;
    private String name;
}




※eureka

package main;
    
@SpringBootApplication
@EnableEurekaServer
public class EurekaMain {
    public static void main(String[] args) {
        SpringApplication.run(EurekaMain.class, args);
    }
}




※application.yml


※provider

server:
  port: 9001
eureka:
  client:
    service-url:
      defaultZone: http://localhost:9051/eureka



※consumer

server:
  port: 80
eureka:
  client:
    service-url:
      defaultZone: http://localhost:9051/eureka



※eureka

server:
  port: 9051
eureka:
  client:
    register-with-eureka: false     # 不註冊自己
    fetch-registry: false     # 取得註冊資訊,因為自己就是註冊中心,不需要取得註冊資訊
    service-url:
      defaultZone: http://localhost:${server.port}/eureka/   # Eureka Server 的位址


※ fetch-registry 一定要 false 才有辦法啟動,否則會報 Cannot execute request on any known server

※ register-with-eureka 如果註冊自己可以順利啟動成功,只是沒什麼意義,感覺就像麥當勞的員工自己也在排隊買漢堡一樣

※server.port 如果給 0,會隨機產生一個,看控製台即可知道


※遇到的問題

invalid LOC header
jar 檔簽名壞了,從倉庫刪除後重新下載即可

Make sure your own configuration does not rely on that class. This can also happen if you are @ComponentScanning a springframework package
main 方法要有 package,還有另外一個錯誤是要有 main 方法,但錯誤訊息很明顯,所以一個專案放一個有 package 的 main 方法就可以了,方法裡什麼都不寫也行



※Eureka 註冊中心

 啟動後會有現都是 UNKNOWN,所以 consumer 和 provider 還要在 application.yml 加上 spring.application.name,這樣 eureka 才會分辯的出來,修改後如下,eureka 會變成全部大寫英文


 如果想改右邊的 Status 顯示的名稱,還得在 consumer 和 provider 加上 eureka.instance.instance-id,修改後如下,這裡 eureka 不會變成大寫


上上一張圖的左下角有網址,是滑鼠在 status 的 consumer 或 provider 上時顯示的,我記得不改是 localhost 之類的,但不知哪一版改了,如果是 localhost 想改成 ip,還可以加上 eureka.instance.prefer-ip-address: true 就可以了
還有一個很像的設定 eureka.instance.ip-address: ,因為有可能有多張網卡,可以寫死其中一張網卡的 IP,如果兩個都設定,最終註冊在 eureka 會是這裡的設定



※啟動順序 

必需先啟動 eureka,再啟動 provider,最後是 consumer
如果 eureka 在啟動前有其他的專案先啟動了,eureka 抓不到
如果 consumer 在 provider 之前,我試的結果沒問題,但理論上是先有提供者,這樣消費者才抓的到
如果已經照順序啟動好了,將 provider 或 consumer 的 spring.application.name 名字換掉,只重啟改掉的專案,會發現 eureka 會有紅色的警告,因為有個 CAP 理論

Consistency(一致性)
Availability(可用性)
Partition tolerance(分区容错性)

目前不可能全部做到,會犧牲一個


eureka 保證 AP
zookeeper 保證 CP

長時間沒有人連 eureka 會報這個警告,或者我將名稱換掉也會,因為他要保證可用性,有可能只是網路不順,寧可記錄錯的資料也不要刪掉,而預設過了 90 秒後,真的都沒有人連了,才會刪除,如果不喜歡這個功能,可以關掉
eureka.server.enable-self-preservation: false


※actuator/info

上面的 Status 一點進去就掛了,這裡是設定裡面的資訊

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
-----------------------------
<build>
    <finalName>SpringCloudPractice</finalName>
    <resources>
        <resource>
            <directory>src/main/resources</directory>
            <filtering>true</filtering>
        </resource>
    </resources>
    
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-resources-plugin</artifactId>
            <configuration>
                <delimiters>
                    <delimit>@</delimit>
                </delimiters>
            </configuration>
        </plugin>
    </plugins>
</build>


※這裡的 pom 是全域的

※delimit 裡面的符號是用在註冊進 eureka 的專案要用到的,前後用這個符號包起來即可

※以下以 provider 為例,在 application.yml 增加如下的設定

info:
  xxx.ooo: xxoo.aa
  name: provider.info
  jdk_version: @java.version@
  version: @version@
  chi_test: 梅山小路用


※info 下的 key 和 value 都可以隨便亂打,但 @ 包起來的部分會去 pom 抓,有自己的就抓自己的,沒有就抓父 pom 的,雖然 provider 有 api,但不會去抓,如果都沒有,啟動會報錯




※服務發現


使用 DiscoveryClient 可以發現 Eureka 上註冊的服務

@Resource
private DiscoveryClient client;
    
@GetMapping("/discovery")
public DiscoveryClient discoveryTest() {
    List<String> services = client.getServices();
    System.out.println("所有服務名稱=" + services);
    
    // List<ServiceInstance> instances = client.getInstances("provider1".toUpperCase()); // 一定要大寫
    services.forEach(serviceName -> {
        List<ServiceInstance> instances = client.getInstances(serviceName);
        instances.forEach(s -> {
            System.out.println("host=" + s.getHost()); // 192.168.254.104
            System.out.println("instanceId=" + s.getInstanceId()); // bruceProvider1
            System.out.println("scheme=" + s.getScheme()); // null
            System.out.println("serviceId=" + s.getServiceId()); // PROVIDER1
            System.out.println("port=" + s.getPort()); //  9001
            System.out.println("uri=" + s.getUri()); // http://192.168.254.104:9001
    
            Map<String, String> metadata = s.getMetadata();
            metadata.forEach((k, v) -> {
                System.out.print("key=" + k); // management.port
                System.out.println(", value=" + v); // 9001
            });
        });
        System.out.println("------------------------------------");
    });
    return client;
}


※在 provider 加上上面的程式碼

※注意 DiscoveryClient是
org.springframework.cloud.client.discovery 的包

※在 provider 的啟動類上加上 @EnableDiscoveryClient,但我試的結果,不加也可以



private static final String PROVIDER_URI = "http://localhost:9001";
    
@GetMapping("/consumer/discovery")
public Object discovery() {
    return restTemplate.getForObject(PROVIDER_URI + "/discovery", Object.class);
}


※provider 最後是要給 consumer 的,所以這裡新增這一段程式碼,但回傳值我給 DiscoveryClient 後,打上網址 localhost/consumer/discovery 會報錯



※Eureka 增加帳號密碼

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>


※在 Eureka server 增加 security 的 POM

※application.yml 增加 spring.security.user,下一層有 name 和 password 可以設定帳號密碼

※如果有集群,每個都要加 pom 和設定密碼,也可以設定不一樣的帳密

※如果只有加 pom,連網址就會出現打帳密的視窗,但因為沒有帳密進不去


@SpringBootApplication
@EnableEurekaServer
public class EurekaMain extends WebSecurityConfigurerAdapter {
    public static void main(String[] args) {
        SpringApplication.run(EurekaMain.class, args);
    }
    
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // http.csrf().disable(); // 不處理 CSRF 攻擊
        http.csrf().ignoringAntMatchers("/eureka/**");
        super.configure(http);
    }
}


※繼承 WebSecurityConfigurerAdapter 後,覆寫 configure,參數是 HttpSecurity 的

※也可以自己寫一個類別繼承 WebSecurityConfigurerAdapte

※不繼承,server 端不會有問題,差在 client 端要連的時候會報 Cannot execute request on any known server 的錯

※用戶端連 eureka 時,只要在 application.yml 的 defaultZone 改一下就可以,如
http://xxx:ooo@xxx.ooo9051:9051/eureka
就是在原本的網址加上「帳號:密碼@」

2019年5月16日 星期四

AtomicXxx、ABA

※AtomicInteger


AtomicInteger ai = new AtomicInteger(); // 建構子可給初始值,不給預設為 0
System.out.println(ai.get()); //取得當前值 0
System.out.println(ai.getAndIncrement()); // 後置遞增 0
System.out.println(ai.incrementAndGet()); // 前置遞增 2
System.out.println(ai.getAndAdd(3)); // 2
System.out.println(ai.addAndGet(3)); // 8
System.out.println(ai.getAndSet(7)); // 8
System.out.println(ai.get()); // 7
ai.set(9); // 回傳 void
System.out.println(ai.get()); // 9

※increment、decrement 為加減1,想加減更多用 addAndGet、getAndSet

※AtomicXxx Xxx 為 Wrapper 類別,都類似這樣的用法



※AtomicIntegerArray

int[] ints = {1, 2};
AtomicIntegerArray arr = new AtomicIntegerArray(ints);
arr.getAndSet(0, 9); // 第0個改成9
System.out.println(arr.get(0));//成功修改
System.out.println(ints[0]); // 原本的陣列並不會被改

※針對陣列的其中一個索引修改



※AtomicReference

MyClass c1 = new MyClass(1, "Tom");
MyClass c2 = new MyClass(2, "Jerry");
    
AtomicReference<MyClass> ref = new AtomicReference<>();
ref.set(c1);
ref.compareAndSet(c1, c2);
System.out.println(ref.get().getId());
System.out.println(ref.get().getName());
    
    
    
@Getter
@Setter
class MyClass {
    private int id;
    private String name;
    public MyClass(int id, String name){
        this.id = id;
        this.name = name;
    }
}

※針對整體物件的修改



※AtomicIntegerFieldUpdater

MyClass c1 = new MyClass(1, "Tom");
    
AtomicIntegerFieldUpdater<MyClass> atomic = AtomicIntegerFieldUpdater.newUpdater(MyClass.class, "id");
System.out.println(atomic.getAndIncrement(c1));
System.out.println(atomic.get(c1));
    
class MyClass {
    volatile int id;
    private String name;
    MyClass(int id, String name){
        this.id = id;
        this.name = name;
    }
}

※針對物件裡的 Integer 修改,當然替換 Integer 也有其他 Wrapper 類別

※注意 Myclass 的欄位必需不是 private 且為 volatile 才可以,會有錯誤提示



※AtomicReferenceFieldUpdater

MyClass c1 = new MyClass(1, "Tom");
    
AtomicReferenceFieldUpdater<MyClass, String> atomic = AtomicReferenceFieldUpdater.newUpdater(MyClass.class, String.class, "name");
atomic.set(c1, "Jerry");
// System.out.println(atomic.getAndSet(c1, "Jerry"));
// System.out.println(atomic.compareAndSet(c1, "Tom", "Jerry")); // 舊值如果不是 Tom 就不更新,回傳 boolean
System.out.println(atomic.get(c1));
    
    
    
class MyClass {
    private int id;
    volatile String name;
    MyClass(int id, String name){
        this.id = id;
        this.name = name;
    }
}

※針對物件裡的物件修改

※使用此方式更新的欄位,一定要使用 Wrapper 類別,如此例 id 不是 Integer 會報錯,必需將 MyClass 和 newUpdate 的第二個參數都使用 Integer 才行



※AtomicStampedReference

Integer i1 = 10;
AtomicStampedReference<Integer> atomic = new AtomicStampedReference<>(i1, 200);
System.out.println(atomic.compareAndSet(10, 11, 200, 201)); // true,預期舊值是 10 且 stamp 是 200 就更新值為11,新的 stamp 為201
System.out.println(i1); // 10
System.out.println(atomic.getReference()); // 11
System.out.println(atomic.getStamp()); // 201
System.out.println(atomic.get(new int[]{i1})); // 11

※針對 ABA 的問題,用此類可解決

※前兩個參數 -128~127 因有 cache,看起來不會有問題,但超過就不行了,永遠是false,就算 i1 改用 int 也會自動裝箱成 Integer
Byte、Short、Integer、Long、Character 都有這個問題,可看 XxxCache
後兩個參數沒有這個問題,因為底層是 int


.較好的寫法

AtomicStampedReference<Integer> atomic = new AtomicStampedReference<>(1000, 200);
System.out.println(atomic.compareAndSet(atomic.getReference(), 11, 200, 201)); // true,以 atomic 為主就是同個記憶體,這樣就不會有問題了
System.out.println(atomic.getReference()); // 11
System.out.println(atomic.getStamp()); // 201

※這樣子寫就不用管 -128~127 的問題了


.使用 AtomicStampedReference 針對整個物件修改

MyClass c1 = new MyClass(1, "Tom");
MyClass c2 = new MyClass(2, "Jerry");
//Integer i1 = 10;
AtomicStampedReference<MyClass> atomic = new AtomicStampedReference<>(c1, 200);
System.out.println(atomic.compareAndSet(atomic.getReference(), c2, 200, 201)); // true
MyClass ref = atomic.getReference();
System.out.println(ref.getId()); // 2
System.out.println(ref.getName()); // Jerry
System.out.println(atomic.getStamp()); // 201
    
    
    
@Getter
@Setter
class MyClass {
    private int id;
    private String name;
    
    MyClass(int id, String name) {
        this.id = id;
        this.name = name;
    }
}



2019年5月15日 星期三

docker

docker version     看版本,我的是18.09.2
docker info           比 version 多很多資訊

docker run hello-world 不加tag,预设为「:latest」
表示執行 hello-world 這個鏡像,有就執行;沒有就去下載並執行

docker search 鏡像名     搜尋鏡像
--no-trunc        不截斷描述
--limit(后接数字,预设为25)       只顯示多少個

docker pull 鏡像名:tag    下載鏡像 (tag不加一樣為 :latest )

tag 要到官網搜尋,以tomcat為例,如下:

每一個都是tag,所以下 docker pull tomcat:7.0.94-jre7 即可
如果 docker search 或 pull 太慢,Windows 10 可在右下叫出 docker 設定,並增加網址,如下:

按 Basic 有他的格式,和 linux 的 /etc/docker/daemon.json 裡一模一樣
再使用 docker info 時,接近最下面有 Registry Mirrors: 可看到,表示設定成功了

docker images 看目前有什麼鏡像
 --all             顯示所有鏡像
--quiet         只顯示 Image ID
--digests      顯示摘要
--no-trunc    不截斷 Image ID

docker rmi   移除鏡像,多個用空格格開
docker rmi --force $(docker images-qa)   一次刪除多個鏡像

docker run -it [imageID|imageName] /bin/bash
exit 離開 Ctrl+P+Q 暫時離開
--name 容器別名(不指定會隨機給一個)
-p      為 port 8888:8080,左邊為想要給的 port;右邊為程式的 port
-P      隨機 port
-d      背後執行,但如果沒有進程在跑,會立即關閉
docker run -d imageID /bin/sh -c "while true; do echo hello ooo; sleep 2; done"

docker ps  顯示正在執行的容器
--all       顯示全部容器,包括執行中,已關閉
--latest  顯示最新的容器
--last 次數  或者 -n 次數    顯示最後幾個容器
--quiet     只顯示容器編號
--size   顯示總大小

docker start 容器名 啟動曾經啟動過的容器
docker restart 容器名
docker stop 溫柔關閉容器
docker kill 強制關閉容器
docker rm 刪除容器 -f 刪還沒啟動的
docker rm --force $(docker ps --all --quiet)
docker ps -all -quiet | xargs docker rm
docker logs --follow -timestamps --tail 容器 id
docker top 容器id      看運行的進程
docker inspect 容器名:看容器內的細節
docker attach 重新進入container
docker exec --tty 容器id linux命令, 加 -tty 會顯示的比較好看,不一樣要有
docker exec /bin/bash 同attach
docker cp 容器id:路徑 本機路徑
docker commit --message=訊息 --author=作者 容器id 命名空間/自定義名稱:tag
因為鏡像一關閉就一切還原了,所以使用 commit 可新增一個自己的鏡像
除了可以使用 commit 外,還可以使用 run 的 -v 參數和 docker file

-v參數:

docker run -it -v /hostFolder: /ContainerFolder imageName /bin/bash
docker run -it -v /hostFolder: /ContainerFolder:ro imageName /bin/bash 容器裡只讀
docker inspect
docker start containerId
ro 是 read Only的意思,不加預設是可讀寫
如果遇到權限問題,可加 --privileged=true 試試
在 Windows10 我沒試出來,都是格式不對的錯



docker file:

from ubuntu    -> ubuntu 為 image 名稱
VOLUME ["/container1", "/container2"]   -> 同 -v,根目錄建兩個資料夾來同步,匿名的方式
cmd echo "finish"
cmd /bin/bash

docker build --file /mydocker/Dockerfile --tag zzyy/ubuntu(namespace) .
如果檔名取 Dockerfile 可不加-f
在 Windows10 也是沒用

-t imageName 可以生成映象檔

RUN yum install -y vim 安裝 vi
RUN yum install -y net-tools 安裝網路相關

WORKDIR 一進入 linux 的路徑

此時主機要交互的資料夾要看 inspect 裡的 volumes

CMD ["ls", "-l"]
ENTRYPOINT ["ls", "-l"]
都是執行命令,但 CMD 是執行而已;而 ENTRYPOINT 是追加
docker run 容器ID -l 可以看出差別

docker run -it --name 子容器名 --volumes-from 父容器名 namespace

https://docs.docker.com/engine/reference/builder/

2019年4月30日 星期二

小心使用 split

String s1 = ",,,";
System.out.println(s1.split(",").length); // 0
String s2 = "a,,,";
System.out.println(s2.split(",").length); // 1
String s3 = ",a,,";
System.out.println(s3.split(",").length); // 2
String s4 = ",,a,";
String[] split = s4.split(",");
System.out.println(split.length); // 3
Arrays.stream(split).forEach(System.out::println); // 空空a
    
String s5 = "abcde";
System.out.println(s5.split("").length); // 5


※只會分割非分割符的前面,如果将 a 改成空格也是一樣的結果

※分割符什麼都沒有時,會每個字符都分割

※s1 長度是 0,所以使用 s1.split(",")[0] 會報 ArrayIndexOutOfBoundsException

2019年4月23日 星期二

分頁插件-PageHelper (Mybatis3.x 十四)

文檔

<dependency>
    <groupId>com.github.pagehelper</groupId>
    <artifactId>pagehelper</artifactId>
    <version>5.1.8</version>
</dependency>


※全域設定

<plugins>
    <plugin interceptor="com.github.pagehelper.PageInterceptor">
        <property name="param1" value="value1" />
    </plugin>
</plugins>


※測試

/*
Page<Object> page = PageHelper.startPage(2, 3); // 每頁三條記錄,顯示第二頁,
List<Emp> emp = mapper.getEmp();
emp.stream().forEach(x -> System.out.println(x.getEname()));
// SELECT * FROM ( SELECT TMP_PAGE.*, ROWNUM ROW_ID FROM ( select * from emp ) TMP_PAGE) WHERE ROW_ID <= ? AND ROW_ID > ? 
System.out.println("當前頁:" + page.getPageNum()); // 2
System.out.println("每頁顯示幾條記錄:" + page.getPageSize()); // 3
System.out.println("總共幾筆記錄:" + page.getTotal());
System.out.println("總共有幾頁:" + page.getPages());
*/
    
PageHelper.startPage(3, 5);
List<Emp> emp = mapper.getEmp();
PageInfo<Emp> pageInfo = new PageInfo<>(emp);
emp.stream().forEach(x -> System.out.println(x.getEname()));
System.out.println("當前頁:" + pageInfo.getPageNum());
System.out.println("每頁顯示幾條記錄:" + pageInfo.getPageSize());
System.out.println("當前頁有幾條記錄(最後一頁時,可能和 PageSize()不同):" + pageInfo.getSize());
System.out.println("總共幾筆記錄:" + pageInfo.getTotal());
System.out.println("總共有幾頁:" + pageInfo.getPages());
    
System.out.println("當前頁的第一筆是全部記錄的第幾筆記錄:" + pageInfo.getStartRow());
System.out.println("當前頁的最後一筆是全部記錄的第幾筆記錄:" + pageInfo.getEndRow());
System.out.println("上一頁是第幾頁:" + pageInfo.getPrePage()); // 沒有上一頁顯示 0
System.out.println("下一頁是第幾頁:" + pageInfo.getNextPage()); // 沒有下一頁顯示 0
System.out.println("是否是第一頁:" + pageInfo.isIsFirstPage());
System.out.println("是否是最後一頁:" + pageInfo.isIsLastPage());
System.out.println("是否有上一頁:" + pageInfo.isHasPreviousPage());
System.out.println("是否有下一頁:" + pageInfo.isHasNextPage());


※PageHelper.startPage 必須寫在呼叫之前才有用

※PageInfo 如果沒有配合 PageHelper,那就是看 SQL 下什麼,回來就有幾筆

※看原始碼其實就有說明每一個欄位的作用了

2019年4月20日 星期六

一、二級快取,整合 Ehcache 2.x (Mybatis3.x 十三)

<setting name="cacheEnabled" value="true" /> <!-- 二級快取的開關 -->
<setting name="localCacheScope" value="STATEMENT" /> <!-- 一級快取的範圍,SESSION為開;STATEMENT為關 -->

※一級快取預設是 SESSION,表示 SqlSession;二級快取預設是 true

※SESSION 表示每個一個 Session 中的所有語句都可以共享; STATEMENT 表示只有當前的  SQL 語句的結果可以共享 (看最下面的一、二級快取)


測試類
InputStream is = null;
SqlSession sqlSession = null;
SqlSession sqlSession2 = null;
try {
    is = Resources.getResourceAsStream("mybatis-config.xml");
    SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(is);
    sqlSession = factory.openSession();
    DeptMapper mapper = sqlSession.getMapper(DeptMapper.class);
    //一級快取
    Dept dept1 = mapper.getDeptById(40);
    //sqlSession.clearCache();清一級快取
    //增刪改操作之後也不會有快取
    Dept dept2 = mapper.getDeptById(40);
    //Dept dept2 = mapper.getDeptByIdAndLoc(40, "BOSTON");
    System.out.println(dept1 == dept2);
    
    //二級快取
    /*
    sqlSession2 = factory.openSession();
    DeptMapper mapper2 = sqlSession2.getMapper(DeptMapper.class);
    Dept dept2 = mapper2.getDeptById(40);
    System.out.println(dept1 == dept2);
    */
}
// ...

注意 dept1 == dept2,如果資料庫沒有值得到的結果是 null,那兩個判斷是 true


※一級快取:

1.指同一個 SqlSession 做查詢的快取
2.只有 where 條件一樣時,才有快取(如上的例子,就算回傳結果一樣,還是沒快取)
兩個查詢之間有增刪改和呼叫 SqlSession 的 clearCache 方法,都不會有快取


※二級快取:

1..指不同 SqlSession 做查詢的快取
2.使用 <cache /> 設定二級快取,其中的屬性可看官網的介紹,其中 readOnly 預設是 false,如果是 false,resultType 的 java bean 必需 implements Serializable,否則會報 java.io.NotSerializableException 的錯,而反序列化後,SQL 就是兩條了,就沒有快取了
3.二個查詢之間必須要將第一個快取 close()後,才會將一級快取裡的值移到二級快取,所以不關閉就不會成功
select 標籤有 useCache,當全域的二級快取是開時,可以針對不想二級快取的 select 關閉
4.<cache-ref> 使用在用另外一支 xml 的 cache


※一、二級快取

在 select 標籤中,還有個 flushCache 可以針對一、二級快取做清除的動作

如果一級快取是 STATEMENT;二級快取是 true,一級快取 close()後會將資料移到二級快取,但一級快取之間還是無法共享

sqlSession = factory.openSession();
DeptMapper mapper = sqlSession.getMapper(DeptMapper.class);
    
//一級快取(SqlSession快取)
Dept dept1 = mapper.getDeptById(40);
Dept dept2 = mapper.getDeptById(40);
System.out.println(dept1 == dept2); // false
    
sqlSession.close();
    
sqlSession2 = factory.openSession();
DeptMapper mapper2 = sqlSession2.getMapper(DeptMapper.class);
    
Dept dept3 = mapper2.getDeptById(40);
//System.out.println(dept1 == dept3);
System.out.println(dept1 == dept3); // false
System.out.println(dept2 == dept3); // true

※如果注解 dept2,那 dept1 == dept3 會是 true,因為 STATEMENT 代表當前的語句



※整合 Ehcache2.x

官方文檔,要下載 ehcache2 和 mybatis 和 ehcache 的 jar 檔, 在根目錄要有 ehcache.xml,可去 ehcache 官網下載,至於設定的意思,可以看到 This file can also be downloaded from 後面有個超連結,會有解釋的;或者 2.x 的 PDF 裡都有
要使用時,只要在二級快取的標籤有個 type,指定為 ehcache 就可以了

<cache readOnly="true" type="org.mybatis.caches.ehcache.EhcacheCache" />

※因為使用 <cache> 標籤,所以還是二級快取,一切的行為都和二級快取一樣,但 ehcache.xml 有一個 <diskStore path="aaa.bbb">,這裡面打的 aaa.bbb 並不是 aaa 目錄下有 bbb 目錄,而是 aaa.bbb 的目錄,會出現在 src 下,也可改成磁碟的路徑,這個檔案就算將二級快取關閉,還是會出現,但是行為還是二級快取的行為

※P.S. 我在整合 Ehcache 3.x 的時候,我試了最低的版本和目前最高的版本,XML 都有問題,都會報 Element <ehcache:config> does not allow attribute "xmlns:ehcache" 或 Element <config> does not allow attribute "xmlns:xsi"