[TOC]
# 簡介
Lettuce 是一個可伸縮的線程安全的 Redis 客戶端,支持同步、異步和響應式模式。多個線程可以共享一個連接實例,而不必擔心多線程并發問題。它基于優秀 netty NIO 框架構建,支持 Redis 的高級功能,如 Sentinel,集群,流水線,自動重新連接和 Redis 數據模型。
# redis單機情況
目前,Lettuce 官方發布的最新的版本為[5.0.4](https://lettuce.io/core/5.0.4.RELEASE/api/),自 5.X 開始,Lettuce 進行了全面重構,與之前的版本相差較大,甚至連包名都全然不同(點擊可查看[5.0.4](https://lettuce.io/core/5.0.4.RELEASE/api/)和[4.4.5](https://lettuce.io/lettuce-4/4.4.5.Final/api/)版本),本文基于最新的版本 5.0.4 介紹 Lettuce 的用法,pom 文件中添加 Lettuce 依賴如下:
~~~
<dependency>
<groupId>io.lettuce</groupId>
<artifactId>lettuce-core</artifactId>
<version>5.0.4.RELEASE</version>
</dependency>
~~~
~~~
import io.lettuce.core.RedisClient;
import io.lettuce.core.RedisURI;
import io.lettuce.core.api.StatefulRedisConnection;
import io.lettuce.core.api.sync.RedisCommands;
public class Single
{
public static void main(String[] args)
{
// 利用redis-server所綁定的IP和Port創建URI,
RedisURI redisURI = RedisURI.create("127.0.0.1", 6379);
// 創建集Redis單機模式客戶端
RedisClient redisClient = RedisClient.create(redisURI);
// 開啟連接
StatefulRedisConnection<String, String> connect = redisClient.connect();
RedisCommands<String, String> cmd = connect.sync();
// set操作,成功則返回OK
cmd.set("key", "value-test");
// get操作,成功命中則返回對應的value,否則返回null
cmd.get("key");
// 刪除指定的key
cmd.del("key");
// 獲取redis-server信息,內容極為豐富
cmd.info();
// 列表操作
String[] valuelist = {"China","Americal","England"};
// 將一個或多個值插入到列表頭部,此處插入多個
cmd.lpush("listName", valuelist);
// 移出并獲取列表的第一個元素
System.out.println(cmd.lpop("listName"));
// 獲取列表長度
System.out.println(cmd.llen("listName"));
// 通過索引獲取列表中的元素
System.out.println(cmd.lindex("listName", 1));
}
}
~~~
注意
如果 redis-server 設置了訪問密碼,在進行緩存讀寫操作之前需要進行鑒權,代碼片段如下:
~~~
// 開啟連接
StatefulRedisConnection<String, String> connect = redisClient.connect();
RedisCommands<String, String> cmd = connect.sync();
// 如果redis-server設置了訪問密碼,則需鑒權,否則不可訪問
cmd.auth("my-password");
// set操作,成功則返回OK
cmd.set("key", "value-test");
~~~
# redis集群模式
首先介紹一個集群模式下的實例,對比單機模式,讀者不難發現,除了創建客戶端差別明顯外,其它部分幾無差別
~~~
import java.util.ArrayList;
import java.util.List;
import io.lettuce.core.RedisURI;
import io.lettuce.core.cluster.RedisClusterClient;
import io.lettuce.core.cluster.api.StatefulRedisClusterConnection;
import io.lettuce.core.cluster.api.sync.RedisClusterCommands;
public class Cluster
{
public static void main(String[] args)
{
// 利用redis-server所綁定的IP和Port創建URI,
List<RedisURI> redisURIList = new ArrayList<RedisURI>();
String[] ipSet = {"100.x.x.152","100.x.x.153","100.x.x.154"};
int port = 6379;
for (int i=0; i<3; i++)
{
RedisURI temp = RedisURI.create(ipSet[i], port);
redisURIList.add(temp);
}
// 創建集Redis集群模式客戶端
RedisClusterClient redisClusterClient = RedisClusterClient.create(redisURIList);
// 連接到Redis集群
StatefulRedisClusterConnection<String, String> clusterCon = redisClusterClient.connect();
// 獲取集群同步命令對象
RedisClusterCommands<String, String> commands = clusterCon.sync();
// set操作,成功則返回OK
commands.set("key", "value-test");
// get操作,成功命中則返回對應的value,否則返回null
commands.get("key");
// 刪除指定的key
commands.del("key");
// 獲取redis-server信息,內容極為豐富
commands.info();
// 列表操作
String[] valuelist = {"China","Americal","England"};
// 將一個或多個值插入到列表頭部,此處插入多個
commands.lpush("listName", valuelist);
// 移出并獲取列表的第一個元素
commands.lpop("listName");
// 獲取列表長度
commands.llen("listName");
// 通過索引獲取列表中的元素
commands.lindex("listName", 1);
}
}
~~~
## 重要接口說明
與單機模式相比,集群模式下命令集要豐富得多,如下圖所示, Lettuce 提供的方法可支持集群模式下的所有命令。其中,有幾個重要的方法讀者需要掌握:clusterAddSlots, clusterFailover, clusterForget, clusterInfo, clusterMeet, clusterNodes 及clusterReplicate

1. clusterMeet(String ip, int port):以當前節點為基準,將 ip 和 port 所對應的節點納入集群;
2. clusterAddSlots(int ...slots):為當前節點指派 slot,只有被指派 slot 的節點才是真正意義上的 master;
3. clusterReplicate(String nodeId):將當前節點設置為 nodeId 所對應的主節點的從;
4. clusterFailover(boolean force):發起故障倒換,將當前節點升為主節點,當前節點原本對應的主節點則降為從節點;
5. clusterForget(String nodeId):將 nodeId 所對應的節點從集群中刪除;
6. clusterInfo():獲取集群運行狀態信息;
7. clusterNodes():獲取集群節點的詳細信息;
## 小技巧
使用 Lettuce時,創建客戶端之后還需連接到集群方可,分別調用了 create() 方法和 connect() 方法,如下代碼片段所示:
~~~
// 創建集Redis集群模式客戶端
RedisClusterClient redisClusterClient = RedisClusterClient.create(redisURIList);
// 連接到Redis集群
StatefulRedisClusterConnection<String, String> clusterCon = redisClusterClient.connect();
~~~
不知讀者是否思考過一個問題:集群連接和單機連接到底有什么區別?為什么一個集群連接就可以操作集群?事實上,所謂集群連接本質上就是一個單機連接的集合,即集群連接包含了到集群中所有節點的連接(單機連接)。既然如此,在集群模式下,當我們需要用到單機連接時,就不必再創建連接了,而是直接從集群連接中“取”出需要的單機連接,這是非常有益的,可以極大的減少資源的消耗,提升性能。如下實例:
~~~
// 創建集Redis集群模式客戶端
RedisClusterClient redisClusterClient = RedisClusterClient.create(redisURIList);
// 連接到Redis集群
StatefulRedisClusterConnection<String, String> clusterCon = redisClusterClient.connect();
// 從集群連接中取出單機連接
// 方式1:根據ip和端口獲取單機連接
StatefulRedisConnection<String, String> conn1 = clusterCon.getConnection(host, port);
// 方式2:根據nodeId獲取單機連接
StatefulRedisConnection<String, String> conn2 = clusterCon.getConnection(nodeId);
~~~
# Lettuce 創建 Redis 集群
Redis 集群模式至少需要 3 個主節點,作為舉例,本文搭建一個 3 主 3 從的精簡集群,麻雀雖小,五臟俱全。主從關系如下圖所示,其中 M 代碼 Master 節點,S 代表 Slave 節點,A-M 和 A-S 為一對主從節點。

由于筆者只有一臺物理機,因此在同一臺機器上分別啟動 6 個 redis-server 進程以創建 3 主 3 從Redis集群,6 個 redis-server 進程分別綁定端口號為 6379,6380,6381,6382,6383,6384
## Redis 集群創建的步驟
**(1)相互感知,初步形成集群**
在上文中,我們已經成功拉起了 6 個 redis-server 進程,每個進程視為一個節點,這些節點仍處于孤立狀態,它們相互之間無法感知對方的存在,既然要創建集群,首先需要讓這些孤立的節點相互感知,形成一個集群;
**(2)分配 Slot 給期望的主節點**
形成集群之后,仍然無法提供服務,Redis 集群模式下,數據存儲于 16384 個 Slot 中,我們需要將這些 Slot 指派給期望的主節點。何為期望呢?我們有 6 個節點,3 主 3 備,我們只能將 Slot 指派給 3 個主節點,至于哪些節點為主節點,我們可以自定義。
**(3)設置從節點**
Slot 分配完成后,被分配 Slot 的節點將成為真正可用的主節點,剩下的沒有分到 Slot 的節點,即便狀態標志為 Master,實際上也不能提供服務。接下來,出于可靠性的考量,我們需要將這些沒有被指派 Slot 的節點指定為可用主節點的從節點(Slave)。
經過上述三個步驟,一個精簡的 3 主 3 從 Redis 集群就搭建完成了。
## 基于 Lettuce 的創建集群代碼
根據上述步驟,基于 Lettuce 創建集群的代碼如下(僅供入門參考):
~~~
import java.util.ArrayList;
import java.util.List;
import io.lettuce.core.RedisClient;
import io.lettuce.core.RedisCommandTimeoutException;
import io.lettuce.core.RedisConnectionException;
import io.lettuce.core.RedisException;
import io.lettuce.core.RedisURI;
import io.lettuce.core.api.StatefulRedisConnection;
public class CreateCluster {
public static void main(String[] args) throws InterruptedException {
createCluster();
}
private static void createCluster() throws InterruptedException {
// 初始化集群節點列表,并指定主節點列表和從節點列表
List<ClusterNode> clusterNodeList = new ArrayList<ClusterNode>();
List<ClusterNode> masterNodeList = new ArrayList<ClusterNode>();
List<ClusterNode> slaveNodeList = new ArrayList<ClusterNode>();
String[] endpoints = {"127.0.0.1:6379", "127.0.0.1:6380", "127.0.0.1:6381"
, "127.0.0.1:6382", "127.0.0.1:6383", "127.0.0.1:6384"};
int index = 0;
for (String endpoint : endpoints) {
String[] ipAndPort = endpoint.split(":");
ClusterNode node = new ClusterNode(ipAndPort[0], Integer.parseInt(ipAndPort[1]));
clusterNodeList.add(node);
// 將6379,6380,6381設置為主節點,其余為從節點
if (index < 3) {
masterNodeList.add(node);
} else {
slaveNodeList.add(node);
}
index++;
}
// 分別與各個Redis節點建立通信連接
for (ClusterNode node : clusterNodeList) {
RedisURI redisUri = RedisURI.Builder.redis(node.getHost(), node.getPort()).build();
RedisClient redisClient = RedisClient.create(redisUri);
try {
StatefulRedisConnection<String, String> connection = redisClient.connect();
node.setConnection(connection);
} catch (RedisException e) {
System.out.println("connection failed-->" + node.getHost() + ":" + node.getPort());
}
}
// 執行cluster meet命令是各個孤立的節點相互感知,初步形成集群。
// 只需以一個節點為基準,讓所有節點與之meet即可
ClusterNode firstNode = null;
for (ClusterNode node : clusterNodeList) {
if (firstNode == null) {
firstNode = node;
} else {
try {
node.getConnection().sync().clusterMeet(firstNode.getHost(), firstNode.getPort());
} catch (RedisCommandTimeoutException | RedisConnectionException e) {
System.out.println("meet failed-->" + node.getHost() + ":" + node.getPort());
}
}
}
// 為主節點指派slot,將16384個slot分成三份:5461,5461,5462
int[] slots = {0, 5460, 5461, 10921, 10922, 16383};
index = 0;
for (ClusterNode node : masterNodeList) {
node.setSlotsBegin(slots[index]);
index++;
node.setSlotsEnd(slots[index]);
index++;
}
// 通過與各個主節點的連接,執行addSlots命令為主節點指派slot
System.out.println("Start to set slots...");
for (ClusterNode node : masterNodeList) {
try {
node.getConnection().sync().clusterAddSlots(createSlots(node.getSlotsBegin(), node.getSlotsEnd()));
} catch (RedisCommandTimeoutException | RedisConnectionException e) {
System.out.println("add slots failed-->" + node.getHost() + ":" + node.getPort());
}
}
// 延時5s,等待slot指派完成
sleep(5000);
// 為已經指派slot的主節點設置從節點,6379,6380,6381分別對應6382,6383,6384
index = 0;
for (ClusterNode node : slaveNodeList) {
try {
node.getConnection().sync().clusterReplicate(masterNodeList.get(index).getMyId());
} catch (RedisCommandTimeoutException | RedisConnectionException e) {
System.out.println("replicate failed-->" + node.getHost() + ":" + node.getPort());
}
}
// 關閉連接,銷毀客戶端,釋放資源
for (ClusterNode node : clusterNodeList) {
node.getConnection().close();
node.getClient().shutdown();
}
}
public static int[] createSlots(int from, int to) {
int[] result = new int[to - from + 1];
int counter = 0;
for (int i = from; i <= to; i++) {
result[counter++] = i;
}
return result;
}
}
~~~
~~~
//定義集群節點描述類
class ClusterNode {
private String host;
private int port;
private int slotsBegin;
private int slotsEnd;
private String myId;
private String masterId;
private StatefulRedisConnection<String, String> connection;
private RedisClient redisClient;
public ClusterNode(String host, int port) {
this.host = host;
this.port = port;
this.slotsBegin = 0;
this.slotsEnd = 0;
this.myId = null;
this.masterId = null;
}
public String getHost() {
return host;
}
public int getPort() {
return port;
}
public void setMaster(String masterId) {
this.masterId = masterId;
}
public String getMaster() {
return masterId;
}
public void setMyId(String myId) {
this.myId = myId;
}
public String getMyId() {
return myId;
}
public void setSlotsBegin(int first) {
this.slotsBegin = first;
}
public void setSlotsEnd(int last) {
this.slotsEnd = last;
}
public int getSlotsBegin() {
return slotsBegin;
}
public int getSlotsEnd() {
return slotsEnd;
}
public void setConnection(StatefulRedisConnection<String, String> connection) {
this.connection = connection;
}
public void setClient(RedisClient client) {
this.redisClient = client;
}
public StatefulRedisConnection<String, String> getConnection() {
return connection;
}
public RedisClient getClient() {
return redisClient;
}
}
~~~
## Lettuce方法獲取并解析集群狀態信息
Lettuce 提供了與 Redis 的 cluster info,cluster nodes 及 info 命令對應的方法,分別為:clusterInfo(), clusterNodes()和info(),是不是覺得很親切?
僅僅只是獲取信息還不夠,如此復雜的信息,雖然人可以一眼看出要點,但用程序來解析卻是一件很麻煩的事情。Lettuce 已經考慮到了這一點,為此提供了專門的方法來解析獲取到的集群節點信息,以 clusterNodes() 為例:

**例子**
一個可用的 Redis 集群,其 16384 個 slot 必須全部處于正常工作狀態,換句話說,這些 slots 對應的 master 必須是正常的。以下我們通過解析 clusterNodes() 方法獲取的信息來判斷集群狀態是否正常,如果不正常,還可以進一步識別出不正常的節點。
**注意**
下面的程序僅僅是舉例,事實上,通過解析 clusterNodes() 方法獲取的信息可以獲取集群節點的運行狀態,主從關系,slot 分布等重要信息。

**例子的完整程序**
~~~
import java.util.ArrayList;
import java.util.List;
import io.lettuce.core.RedisURI;
import io.lettuce.core.cluster.RedisClusterClient;
import io.lettuce.core.cluster.api.StatefulRedisClusterConnection;
import io.lettuce.core.cluster.api.sync.RedisClusterCommands;
import io.lettuce.core.cluster.models.partitions.ClusterPartitionParser;
import io.lettuce.core.cluster.models.partitions.Partitions;
import io.lettuce.core.cluster.models.partitions.RedisClusterNode;
public class ClusterState {
public static void main(String[] args) {
// 利用redis-server所綁定的IP和Port創建URI,
List<RedisURI> redisURIList = new ArrayList<RedisURI>();
// 筆者在一臺物理機上啟動6個redis-server進程,ip均為127.X,端口為6379~6384
String ip = "127.0.0.1";
int port = 6379;
for (int i = 0; i < 6; i++) {
RedisURI temp = RedisURI.create(ip, port + i);
redisURIList.add(temp);
}
// 創建集Redis集群模式客戶端
RedisClusterClient redisClusterClient = RedisClusterClient.create(redisURIList);
// 連接到Redis集群
StatefulRedisClusterConnection<String, String> clusterCon = redisClusterClient.connect();
// 獲取集群同步命令對象
RedisClusterCommands<String, String> commands = clusterCon.sync();
// 獲取集群節點信息并解析
Partitions partitions = ClusterPartitionParser.parse(commands.clusterNodes());
int slotsSize = 0;
for (RedisClusterNode partition : partitions) {
if (partition.getFlags().contains(RedisClusterNode.NodeFlag.FAIL)
|| partition.getFlags().contains(RedisClusterNode.NodeFlag.EVENTUAL_FAIL)
|| partition.getFlags().contains(RedisClusterNode.NodeFlag.NOADDR)) {
System.out.println("The node's state is not normal:" + partition.getUri());
continue;
}
slotsSize += partition.getSlots().size();
}
if (slotsSize < 16384) {
System.out.println("Cluster_slots_assigned is:" + slotsSize);
} else {
System.out.println("Cluster_state is OK.");
}
}
}
~~~
# 遇到的問題
**堆內存溢出事件**
在實際應用場景下,Redis 集群可能出現節點故障下線、新節點加入、主從倒換等事件,這些事件都會導致 Redis 集群拓撲結構改變,作為客戶端的 Lettuce 自然也需要刷新保存的拓撲結構甚至重新建立連接,否則,客戶端與服務端之間的通道可能無法工作。
出于對上述原因考慮,為提高可用性,筆者曾經主導過的一個項目通過一個線程來定時檢測連接是否可用,如果不可用便重建連接。但是,當時犯了一個錯誤:重建連接時,僅僅關閉了舊的連接,卻沒有銷毀客戶端,而客戶端是極為占用資源的。
由于連接不可用的場景并不多,上述問題一直處于潛伏狀態,直到有一天網絡出現問題,因連接不可用而一次次重建連接,同時重建了客戶端。一段時間后,Lettuce 相關的線程竟堆積了近 300 個,而相關進程預設的內存不過 2G,進而出現了內存溢出。
**規避方法:**
簡而言之,對于不再使用的客戶端和連接一定要顯示的關閉,如下代碼所示:

- 基礎
- 編譯和安裝
- classpath到底是什么?
- 編譯運行
- 安裝
- sdkman多版本
- jabba多版本
- java字節碼查看
- 數據類型
- 簡介
- 整形
- char和int
- 變量和常量
- 大數值運算
- 基本類型包裝類
- Math類
- 內存劃分
- 位運算符
- 方法相關
- 方法重載
- 可變參數
- 方法引用
- 面向對象
- 定義
- 繼承和覆蓋
- 接口和抽象類
- 接口定義增強
- 內建函數式接口
- 多態
- 泛型
- final和static
- 內部類
- 包
- 修飾符
- 異常
- 枚舉類
- 代碼塊
- 對象克隆
- BeanUtils
- java基礎類
- scanner類
- Random類
- System類
- Runtime類
- Comparable接口
- Comparator接口
- MessageFormat類
- NumberFormat
- 數組相關
- 數組
- Arrays
- string相關
- String
- StringBuffer
- StringBuilder
- 正則
- 日期類
- Locale類
- Date
- DateFormat
- SimpleDateFormat
- Calendar
- 新時間日期API
- 簡介
- LocalDate,LocalTime,LocalDateTime
- Instant時間點
- 帶時區的日期,時間處理
- 時間間隔
- 日期時間校正器
- TimeUnit
- 用yyyy
- 集合
- 集合和迭代器
- ArrayList集合
- List
- Set
- 判斷集合唯一
- Map和Entry
- stack類
- Collections集合工具類
- Stream數據流
- foreach不能修改內部元素
- of方法
- IO
- File類
- 字節流stream
- 字符流Reader
- IO流分類
- 轉換流
- 緩沖流
- 流的操作規律
- properties
- 序列化流與反序列化流
- 打印流
- System類對IO支持
- commons-IO
- IO流總結
- NIO
- 異步與非阻塞
- IO通信
- Unix的IO模型
- epoll對于文件描述符操作模式
- 用戶空間和內核空間
- NIO與普通IO的主要區別
- Paths,Path,Files
- Buffer
- Channel
- Selector
- Pipe
- Charset
- NIO代碼
- 多線程
- 創建線程
- 線程常用方法
- 線程池相關
- 線程池概念
- ThreadPoolExecutor
- Runnable和Callable
- 常用的幾種線程池
- 線程安全
- 線程同步的幾種方法
- synchronized
- 死鎖
- lock接口
- ThreadLoad
- ReentrantLock
- 讀寫鎖
- 鎖的相關概念
- volatile
- 釋放鎖和不釋放鎖的操作
- 等待喚醒機制
- 線程狀態
- 守護線程和普通線程
- Lamda表達式
- 反射相關
- 類加載器
- 反射
- 注解
- junit注解
- 動態代理
- 網絡編程相關
- 簡介
- UDP
- TCP
- 多線程socket上傳圖片
- NIO
- JDBC相關
- JDBC
- 預處理
- 批處理
- 事務
- properties配置文件
- DBUtils
- DBCP連接池
- C3P0連接池
- 獲得MySQL自動生成的主鍵
- Optional類
- Jigsaw模塊化
- 日志相關
- JDK日志
- log4j
- logback
- xml
- tomcat
- maven
- 簡介
- 倉庫
- 目錄結構
- 常用命令
- 生命周期
- idea配置
- jar包沖突
- 依賴范圍
- 私服
- 插件
- git-commit-id-plugin
- maven-assembly-plugin
- maven-resources-plugin
- maven-compiler-plugin
- versions-maven-plugin
- maven-source-plugin
- tomcat-maven-plugin
- 多環境
- 自定義插件
- stream
- swing
- json
- jackson
- optional
- junit
- gradle
- servlet
- 配置
- ServletContext
- 生命周期
- HttpServlet
- request
- response
- 亂碼
- session和cookie
- cookie
- session
- jsp
- 簡介
- 注釋
- 方法,成員變量
- 指令
- 動作標簽
- 隱式對象
- EL
- JSTL
- javaBean
- listener監聽器
- Filter過濾器
- 圖片驗證碼
- HttpUrlConnection
- 國際化
- 文件上傳
- 文件下載
- spring
- 簡介
- Bean
- 獲取和實例化
- 屬性注入
- 自動裝配
- 繼承和依賴
- 作用域
- 使用外部屬性文件
- spel
- 前后置處理器
- 生命周期
- 掃描規則
- 整合多個配置文件
- 注解
- 簡介
- 注解分層
- 類注入
- 分層和作用域
- 初始化方法和銷毀方法
- 屬性
- 泛型注入
- Configuration配置文件
- aop
- aop的實現
- 動態代理實現
- cglib代理實現
- aop名詞
- 簡介
- aop-xml
- aop-注解
- 代理方式選擇
- jdbc
- 簡介
- JDBCTemplate
- 事務
- 整合
- junit整合
- hibernate
- 簡介
- hibernate.properties
- 實體對象三種狀態
- 檢索方式
- 簡介
- 導航對象圖檢索
- OID檢索
- HQL
- Criteria(QBC)
- Query
- 緩存
- 事務管理
- 關系映射
- 注解
- 優化
- MyBatis
- 簡介
- 入門程序
- Mapper動態代理開發
- 原始Dao開發
- Mapper接口開發
- SqlMapConfig.xml
- map映射文件
- 輸出返回map
- 輸入參數
- pojo包裝類
- 多個輸入參數
- resultMap
- 動態sql
- 關聯
- 一對一
- 一對多
- 多對多
- 整合spring
- CURD
- 占位符和sql拼接以及參數處理
- 緩存
- 延遲加載
- 注解開發
- springMVC
- 簡介
- RequestMapping
- 參數綁定
- 常用注解
- 響應
- 文件上傳
- 異常處理
- 攔截器
- springBoot
- 配置
- 熱更新
- java配置
- springboot配置
- yaml語法
- 運行
- Actuator 監控
- 多環境配置切換
- 日志
- 日志簡介
- logback和access
- 日志文件配置屬性
- 開機自啟
- aop
- 整合
- 整合Redis
- 整合Spring Data JPA
- 基本查詢
- 復雜查詢
- 多數據源的支持
- Repository分析
- JpaSpeci?cationExecutor
- 整合Junit
- 整合mybatis
- 常用注解
- 基本操作
- 通用mapper
- 動態sql
- 關聯映射
- 使用xml
- spring容器
- 整合druid
- 整合郵件
- 整合fastjson
- 整合swagger
- 整合JDBC
- 整合spingboot-cache
- 請求
- restful
- 攔截器
- 常用注解
- 參數校驗
- 自定義filter
- websocket
- 響應
- 異常錯誤處理
- 文件下載
- 常用注解
- 頁面
- Thymeleaf組件
- 基本對象
- 內嵌對象
- 上傳文件
- 單元測試
- 模擬請求測試
- 集成測試
- 源碼解析
- 自動配置原理
- 啟動流程分析
- 源碼相關鏈接
- Servlet,Filter,Listener
- springcloud
- 配置
- 父pom
- 創建子工程
- Eureka
- Hystrix
- Ribbon
- Feign
- Zuul
- kotlin
- 基本數據類型
- 函數
- 區間
- 區塊鏈
- 簡介
- linux
- ulimit修改
- 防止syn攻擊
- centos7部署bbr
- debain9開啟bbr
- mysql
- 隔離性
- sql執行加載順序
- 7種join
- explain
- 索引失效和優化
- 表連接優化
- orderby的filesort問題
- 慢查詢
- show profile
- 全局查詢日志
- 死鎖解決
- sql
- 主從
- IDEA
- mac快捷鍵
- 美化界面
- 斷點調試
- 重構
- springboot-devtools熱部署
- IDEA進行JAR打包
- 導入jar包
- ProjectStructure
- toString添加json模板
- 配置maven
- Lombok插件
- rest client
- 文檔顯示
- sftp文件同步
- 書簽
- 代碼查看和搜索
- postfix
- live template
- git
- 文件頭注釋
- JRebel
- 離線模式
- xRebel
- github
- 連接mysql
- 選項沒有Java class的解決方法
- 擴展
- 項目配置和web部署
- 前端開發
- json和Inject language
- idea內存和cpu變高
- 相關設置
- 設計模式
- 單例模式
- 簡介
- 責任鏈
- JUC
- 原子類
- 原子類簡介
- 基本類型原子類
- 數組類型原子類
- 引用類型原子類
- JVM
- JVM規范內存解析
- 對象的創建和結構
- 垃圾回收
- 內存分配策略
- 備注
- 虛擬機工具
- 內存模型
- 同步八種操作
- 內存區域大小參數設置
- happens-before
- web service
- tomcat
- HTTPS
- nginx
- 變量
- 運算符
- 模塊
- Rewrite規則
- Netty
- netty為什么沒用AIO
- 基本組件
- 源碼解讀
- 簡單的socket例子
- 準備netty
- netty服務端啟動
- 案例一:發送字符串
- 案例二:發送對象
- websocket
- ActiveMQ
- JMS
- 安裝
- 生產者-消費者代碼
- 整合springboot
- kafka
- 簡介
- 安裝
- 圖形化界面
- 生產過程分析
- 保存消息分析
- 消費過程分析
- 命令行
- 生產者
- 消費者
- 攔截器interceptor
- partition
- kafka為什么快
- kafka streams
- kafka與flume整合
- RabbitMQ
- AMQP
- 整體架構
- RabbitMQ安裝
- rpm方式安裝
- 命令行和管控頁面
- 消息生產與消費
- 整合springboot
- 依賴和配置
- 簡單測試
- 多方測試
- 對象支持
- Topic Exchange模式
- Fanout Exchange訂閱
- 消息確認
- java client
- RabbitAdmin和RabbitTemplate
- 兩者簡介
- RabbitmqAdmin
- RabbitTemplate
- SimpleMessageListenerContainer
- MessageListenerAdapter
- MessageConverter
- 詳解
- Jackson2JsonMessageConverter
- ContentTypeDelegatingMessageConverter
- lucene
- 簡介
- 入門程序
- luke查看索引
- 分析器
- 索引庫維護
- elasticsearch
- 配置
- 插件
- head插件
- ik分詞插件
- 常用術語
- Mapping映射
- 數據類型
- 屬性方法
- Dynamic Mapping
- Index Template 索引模板
- 管理映射
- 建立映射
- 索引操作
- 單模式下CURD
- mget多個文檔
- 批量操作
- 版本控制
- 基本查詢
- Filter過濾
- 組合查詢
- 分析器
- redis
- String
- list
- hash
- set
- sortedset
- 發布訂閱
- 事務
- 連接池
- 管道
- 分布式可重入鎖
- 配置文件翻譯
- 持久化
- RDB
- AOF
- 總結
- Lettuce
- zookeeper
- zookeeper簡介
- 集群部署
- Observer模式
- 核心工作機制
- zk命令行操作
- zk客戶端API
- 感知服務動態上下線
- 分布式共享鎖
- 原理
- zab協議
- 兩階段提交協議
- 三階段提交協議
- Paxos協議
- ZAB協議
- hadoop
- 簡介
- hadoop安裝
- 集群安裝
- 單機安裝
- linux編譯hadoop
- 添加新節點
- 退役舊節點
- 集群間數據拷貝
- 歸檔
- 快照管理
- 回收站
- 檢查hdfs健康狀態
- 安全模式
- hdfs簡介
- hdfs命令行操作
- 常見問題匯總
- hdfs客戶端操作
- mapreduce工作機制
- 案例-單詞統計
- 局部聚合Combiner
- combiner流程
- combiner案例
- 自定義排序
- 自定義Bean對象
- 排序的分類
- 案例-按總量排序需求
- 一次性完成統計和排序
- 分區
- 分區簡介
- 案例-結果分區
- 多表合并
- reducer端合并
- map端合并(分布式緩存)
- 分組
- groupingComparator
- 案例-求topN
- 全局計數器
- 合并小文件
- 小文件的弊端
- CombineTextInputFormat機制
- 自定義InputFormat
- 自定義outputFormat
- 多job串聯
- 倒排索引
- 共同好友
- 串聯
- 數據壓縮
- InputFormat接口實現類
- yarn簡介
- 推測執行算法
- 本地提交到yarn
- 框架運算全流程
- 數據傾斜問題
- mapreduce的優化方案
- HA機制
- 優化
- Hive
- 安裝
- shell參數
- 數據類型
- 集合類型
- 數據庫
- DDL操作
- 創建表
- 修改表
- 分區表
- 分桶表
- DML操作
- load
- insert
- select
- export,import
- Truncate
- 注意
- 嚴格模式
- 函數
- 內置運算符
- 內置函數
- 自定義函數
- Transfrom實現
- having和where不同
- 壓縮
- 存儲
- 存儲和壓縮結合使用
- explain詳解
- 調優
- Fetch抓取
- 本地模式
- 表的優化
- GroupBy
- count(Distinct)去重統計
- 行列過濾
- 動態分區調整
- 數據傾斜
- 并行執行
- JVM重用
- 推測執行
- reduce內存和個數
- sql查詢結果作為變量(shell)
- youtube
- flume
- 簡介
- 安裝
- 常用組件
- 攔截器
- 案例
- 監聽端口到控制臺
- 采集目錄到HDFS
- 采集文件到HDFS
- 多個agent串聯
- 日志采集和匯總
- 單flume多channel,sink
- 自定義攔截器
- 高可用配置
- 使用注意
- 監控Ganglia
- sqoop
- 安裝
- 常用命令
- 數據導入
- 準備數據
- 導入數據到HDFS
- 導入關系表到HIVE
- 導入表數據子集
- 增量導入
- 數據導出
- 打包腳本
- 作業
- 原理
- azkaban
- 簡介
- 安裝
- 案例
- 簡介
- command類型單一job
- command類型多job工作流flow
- HDFS操作任務
- mapreduce任務
- hive腳本任務
- oozie
- 安裝
- hbase
- 簡介
- 系統架構
- 物理存儲
- 尋址機制
- 讀寫過程
- 安裝
- 命令行
- 基本CURD
- java api
- CURD
- CAS
- 過濾器查詢
- 建表高級屬性
- 與mapreduce結合
- 與sqoop結合
- 協處理器
- 參數配置優化
- 數據備份和恢復
- 節點管理
- 案例-點擊流
- 簡介
- HUE
- 安裝
- storm
- 簡介
- 安裝
- 集群啟動及任務過程分析
- 單詞統計
- 單詞統計(接入kafka)
- 并行度和分組
- 啟動流程分析
- ACK容錯機制
- ACK簡介
- BaseRichBolt簡單使用
- BaseBasicBolt簡單使用
- Ack工作機制
- 本地目錄樹
- zookeeper目錄樹
- 通信機制
- 案例
- 日志告警
- 工具
- YAPI
- chrome無法手動拖動安裝插件
- 時間和空間復雜度
- jenkins
- 定位cpu 100%
- 常用腳本工具
- OOM問題定位
- scala
- 編譯
- 基本語法
- 函數
- 數組常用方法
- 集合
- 并行集合
- 類
- 模式匹配
- 異常
- tuple元祖
- actor并發編程
- 柯里化
- 隱式轉換
- 泛型
- 迭代器
- 流stream
- 視圖view
- 控制抽象
- 注解
- spark
- 企業架構
- 安裝
- api開發
- mycat
- Groovy
- 基礎