這篇文章,我們開始 Spring AMQP 項目實戰旅程。
## 介紹
通過這個項目實戰旅程,你會學習到如何使用 Spring Boot 整合 Spring AMQP,并且使用 RabbitMQ 的消息隊列機制發送郵件。其中,消息生產者負責將用戶的郵件消息發送至消息隊列,而消息消費者從消息隊列中獲取郵件消息進行發送。這個過程,你可以理解成郵局:當你將要發布的郵件放在郵箱中時,您可以確信郵差最終會將郵件發送給收件人。
## 準備
本教程假定 RabbitMQ 已在標準端口(5672) 的 localhost 上安裝并運行。如果使用不同的主機,端口,連接設置將需要調整。
~~~null
host = localhost
·
password = guest
port = 5672
vhost = /
~~~
## 實戰旅程
### 準備工作
這個實戰教程會構建兩個工程項目:email-server-producer 與 email-server-consumer。其中,email-server-producer 是消息生產者工程,email-server-consumer 是消息消費者工程。
**在教程的最后,我會將完整的代碼提交至 github 上面,你可以結合源碼來閱讀這個教程,會有更好的效果。**
現在開始旅程吧。我們使用 Spring Boot 整合 Spring AMQP,并通過 Maven 構建依賴關系。(由于篇幅的問題,我并不會粘貼完整的 pom.xml 配置信息,你可以在 github 源碼中查看完整的配置文件)
~~~xml
<dependencies>
<!-- spring boot-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
</dependency>
<dependency>
<groupId>javax.mail</groupId>
<artifactId>mail</artifactId>
<version>${javax.mail.version}</version>
</dependency>
</dependencies>
~~~
### 構建消息生產者
[](https://gitee.com/chenssy/blog-home/raw/master/image/201810/spring-amqp-email-list1.PNG)
我們使用 Java Config 的方式配置消息生產者。
~~~java
@Configuration
@ComponentScan(basePackages = {"com.lianggzone.rabbitmq"})
@PropertySource(value = {"classpath:application.properties"})
public class RabbitMQConfig {
@Autowired
private Environment env;
@Bean
public ConnectionFactory connectionFactory() throws Exception {
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost(env.getProperty("mq.host").trim());
connectionFactory.setPort(Integer.parseInt(env.getProperty("mq.port").trim()));
connectionFactory.setVirtualHost(env.getProperty("mq.vhost").trim());
connectionFactory.setUsername(env.getProperty("mq.username").trim());
connectionFactory.setPassword(env.getProperty("mq.password").trim());
return connectionFactory;
}
@Bean
public CachingConnectionFactory cachingConnectionFactory() throws Exception {
return new CachingConnectionFactory(connectionFactory());
}
@Bean
public RabbitTemplate rabbitTemplate() throws Exception {
RabbitTemplate rabbitTemplate = new RabbitTemplate(cachingConnectionFactory());
rabbitTemplate.setChannelTransacted(true);
return rabbitTemplate;
}
@Bean
public AmqpAdmin amqpAdmin() throws Exception {
return new RabbitAdmin(cachingConnectionFactory());
}
@Bean
Queue queue() {
String name = env.getProperty("mq.queue").trim();
// 是否持久化
boolean durable = StringUtils.isNotBlank(env.getProperty("mq.queue.durable").trim())?
Boolean.valueOf(env.getProperty("mq.queue.durable").trim()) : true;
// 僅創建者可以使用的私有隊列,斷開后自動刪除
boolean exclusive = StringUtils.isNotBlank(env.getProperty("mq.queue.exclusive").trim())?
Boolean.valueOf(env.getProperty("mq.queue.exclusive").trim()) : false;
// 當所有消費客戶端連接斷開后,是否自動刪除隊列
boolean autoDelete = StringUtils.isNotBlank(env.getProperty("mq.queue.autoDelete").trim())?
Boolean.valueOf(env.getProperty("mq.queue.autoDelete").trim()) : false;
return new Queue(name, durable, exclusive, autoDelete);
}
@Bean
TopicExchange exchange() {
String name = env.getProperty("mq.exchange").trim();
// 是否持久化
boolean durable = StringUtils.isNotBlank(env.getProperty("mq.exchange.durable").trim())?
Boolean.valueOf(env.getProperty("mq.exchange.durable").trim()) : true;
// 當所有消費客戶端連接斷開后,是否自動刪除隊列
boolean autoDelete = StringUtils.isNotBlank(env.getProperty("mq.exchange.autoDelete").trim())?
Boolean.valueOf(env.getProperty("mq.exchange.autoDelete").trim()) : false;
return new TopicExchange(name, durable, autoDelete);
}
@Bean
Binding binding() {
String routekey = env.getProperty("mq.routekey").trim();
return BindingBuilder.bind(queue()).to(exchange()).with(routekey);
}
}
~~~
其中,定義了隊列、交換器,以及綁定。事實上,通過這種方式當隊列或交換器不存在的時候,Spring AMQP 會自動創建它。(如果你不希望自動創建,可以在 RabbitMQ 的管理后臺開通隊列和交換器,并注釋掉 queue() 方法和 exchange() 方法)。此外,我們為了更好地擴展,將創建隊列或交換器的配置信息抽離到了配置文件 application.properties。其中,還包括 RabbitMQ 的配置信息。
~~~null
mq.host=localhost
mq.username=guest
mq.password=guest
mq.port=5672
mq.vhost=/
mq.exchange=email_exchange
mq.exchange.durable=true
mq.exchange.autoDelete=false
mq.queue=email_queue
mq.queue.durable=true
mq.queue.exclusive=false
mq.queue.autoDelete=false
mq.routekey=email_routekey
~~~
此外,假設一個生產者發送到一個交換器,而一個消費者從一個隊列接收消息。此時,將隊列綁定到交換器對于連接這些生產者和消費者至關重要。在 Spring AMQP 中,我們定義一個 Binding 類來表示這些連接。我們使用 BindingBuilder 來構建 “流式的 API” 風格。
~~~java
BindingBuilder.bind(queue()).to(exchange()).with(routekey);
~~~
現在,我們離大功告成已經很近了,需要再定義一個發送郵件任務存入消息隊列的方法。此時,為了更好地擴展,我們定義一個接口和一個實現類,基于接口編程嘛。
~~~java
public interface EmailService {
/**
* 發送郵件任務存入消息隊列
* @param message
* @throws Exception
*/
void sendEmail(String message) throws Exception;
}
~~~
它的實現類中重寫 sendEmail() 方法,將消息轉碼并寫入到消息隊列中。
~~~java
@Service
public class EmailServiceImpl implements EmailService{
private static Logger logger = LoggerFactory.getLogger(EmailServiceImpl.class);
@Resource( name = "rabbitTemplate" )
private RabbitTemplate rabbitTemplate;
@Value("${mq.exchange}")
private String exchange;
@Value("${mq.routekey}")
private String routeKey;
@Override
public void sendEmail(String message) throws Exception {
try {
rabbitTemplate.convertAndSend(exchange, routeKey, message);
}catch (Exception e){
logger.error("EmailServiceImpl.sendEmail", ExceptionUtils.getMessage(e));
}
}
}
~~~
那么,我們再模擬一個 RESTful API 接口調用的場景,來模擬真實的場景。
~~~java
@RestController()
@RequestMapping(value = "/v1/emails")
public class EmailController {
@Resource
private EmailService emailService;
@RequestMapping(method = RequestMethod.POST)
public JSONObject add(@RequestBody JSONObject jsonObject) throws Exception {
emailService.sendEmail(jsonObject.toJSONString());
return jsonObject;
}
}
~~~
最后,再寫一個 main 方法,將 Spring Boot 服務運行起來吧。
~~~java
@RestController
@EnableAutoConfiguration
@ComponentScan(basePackages = {"com.lianggzone.rabbitmq"})
public class WebMain {
public static void main(String[] args) throws Exception {
SpringApplication.run(WebMain.class, args);
}
}
~~~
至此,已經大功告成了。我們可以通過 Postman 發送一個 HTTP 請求。(Postman是一款功能強大的網頁調試與發送網頁HTTP請求的Chrome插件。)
~~~null
{
"to":"lianggzone@163.com",
"subject":"email-server-producer",
"text":"<html><head></head><body><h1>郵件測試</h1><p>hello!this is mail test。</p></body></html>"
}
~~~
請參見圖示。
[](https://gitee.com/chenssy/blog-home/raw/master/image/201810/spring-amqp-email.PNG)
來看看 RabbitMQ 的管理后臺吧,它會出現一個未處理的消息。(地址:http://localhost:15672/#/queues)
[](https://gitee.com/chenssy/blog-home/raw/master/image/201810/spring-amqp-email-admin.PNG)
注意的是,千萬別向我的郵箱發測試消息喲,不然我的郵箱會郵件爆炸的/(ㄒoㄒ)/~。~
### 構建消息消費者
[](https://gitee.com/chenssy/blog-home/raw/master/image/201810/spring-amqp-email-list2.PNG)
完成消息生產者之后,我們再來構建一個消息消費者的工程。同樣地,我們使用 Java Config 的方式配置消息消費者。
~~~java
@Configuration
@ComponentScan(basePackages = {"com.lianggzone.rabbitmq"})
@PropertySource(value = {"classpath:application.properties"})
public class RabbitMQConfig {
@Autowired
private Environment env;
@Bean
public ConnectionFactory connectionFactory() throws Exception {
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost(env.getProperty("mq.host").trim());
connectionFactory.setPort(Integer.parseInt(env.getProperty("mq.port").trim()));
connectionFactory.setVirtualHost(env.getProperty("mq.vhost").trim());
connectionFactory.setUsername(env.getProperty("mq.username").trim());
connectionFactory.setPassword(env.getProperty("mq.password").trim());
return connectionFactory;
}
@Bean
public CachingConnectionFactory cachingConnectionFactory() throws Exception {
return new CachingConnectionFactory(connectionFactory());
}
@Bean
public RabbitTemplate rabbitTemplate() throws Exception {
RabbitTemplate rabbitTemplate = new RabbitTemplate(cachingConnectionFactory());
rabbitTemplate.setChannelTransacted(true);
return rabbitTemplate;
}
@Bean
public AmqpAdmin amqpAdmin() throws Exception {
return new RabbitAdmin(cachingConnectionFactory());
}
@Bean
public SimpleMessageListenerContainer listenerContainer(
@Qualifier("mailMessageListenerAdapter") MailMessageListenerAdapter mailMessageListenerAdapter) throws Exception {
String queueName = env.getProperty("mq.queue").trim();
SimpleMessageListenerContainer simpleMessageListenerContainer =
new SimpleMessageListenerContainer(cachingConnectionFactory());
simpleMessageListenerContainer.setQueueNames(queueName);
simpleMessageListenerContainer.setMessageListener(mailMessageListenerAdapter);
// 設置手動 ACK
simpleMessageListenerContainer.setAcknowledgeMode(AcknowledgeMode.MANUAL);
return simpleMessageListenerContainer;
}
}
~~~
聰明的你,應該發現了其中的不同。這個代碼中多了一個 listenerContainer() 方法。是的,它是一個監聽器容器,用來監聽消息隊列進行消息處理的。注意的是,我們這里設置手動 ACK 的方式。默認的情況下,它采用自動應答,這種方式中消息隊列會發送消息后立即從消息隊列中刪除該消息。此時,我們通過手動 ACK 方式,如果消費者因宕機或鏈接失敗等原因沒有發送 ACK,RabbitMQ 會將消息重新發送給其他監聽在隊列的下一個消費者,保證消息的可靠性。
當然,我們也定義 application.properties 配置文件。
~~~null
mq.host=localhost
mq.username=guest
mq.password=guest
mq.port=5672
mq.vhost=/
mq.queue=email_queue
~~~
此外,我們創建了一個 MailMessageListenerAdapter 類來消費消息。
~~~java
@Component("mailMessageListenerAdapter")
public class MailMessageListenerAdapter extends MessageListenerAdapter {
@Resource
private JavaMailSender mailSender;
@Value("${mail.username}")
private String mailUsername;
@Override
public void onMessage(Message message, Channel channel) throws Exception {
try {
// 解析RabbitMQ消息體
String messageBody = new String(message.getBody());
MailMessageModel mailMessageModel = JSONObject.toJavaObject(JSONObject.parseObject(messageBody), MailMessageModel.class);
// 發送郵件
String to = mailMessageModel.getTo();
String subject = mailMessageModel.getSubject();
String text = mailMessageModel.getText();
sendHtmlMail(to, subject, text);
// 手動ACK
channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
}catch (Exception e){
e.printStackTrace();
}
}
/**
* 發送郵件
* @param to
* @param subject
* @param text
* @throws Exception
*/
private void sendHtmlMail(String to, String subject, String text) throws Exception {
MimeMessage mimeMessage = mailSender.createMimeMessage();
MimeMessageHelper mimeMessageHelper = new MimeMessageHelper(mimeMessage);
mimeMessageHelper.setFrom(mailUsername);
mimeMessageHelper.setTo(to);
mimeMessageHelper.setSubject(subject);
mimeMessageHelper.setText(text, true);
// 發送郵件
mailSender.send(mimeMessage);
}
}
~~~
在 onMessage() 方法中,我們完成了三件事情:
1\. 從 RabbitMQ 的消息隊列中解析消息體。
1\. 根據消息體的內容,發送郵件給目標的郵箱。
1\. 手動應答 ACK,讓消息隊列刪除該消息。
這里,JSONObject.toJavaObject() 方法使用 fastjson 將 json 字符串轉換成實體對象 MailMessageModel。注意的是,@Data 是 lombok 類庫的一個注解。
~~~java
@Data
public class MailMessageModel {
@JSONField(name = "from")
private String from;
@JSONField(name = "to")
private String to;
@JSONField(name = "subject")
private String subject;
@JSONField(name = "text")
private String text;
@Override
public String toString() {
StringBuffer sb = new StringBuffer();
sb.append("Email{from:").append(this.from).append(", ");
sb.append("to:").append(this.to).append(", ");
sb.append("subject:").append(this.subject).append(", ");
sb.append("text:").append(this.text).append("}");
return sb.toString();
}
}
~~~
Spring 對 Java Mail 有很好的支持。其中,郵件包括幾種類型:簡單文本的郵件、 HTML 文本的郵件、 內嵌圖片的郵件、 包含附件的郵件。這里,我們封裝了一個簡單的 sendHtmlMail() 進行郵件發送。
對了,我們還少了一個郵件的配置類。
~~~java
@Configuration
@PropertySource(value = {"classpath:mail.properties"})
@ComponentScan(basePackages = {"com.lianggzone.rabbitmq"})
public class EmailConfig {
@Autowired
private Environment env;
@Bean(name = "mailSender")
public JavaMailSender mailSender() {
// 創建郵件發送器, 主要提供了郵件發送接口、透明創建Java Mail的MimeMessage、及郵件發送的配置
JavaMailSenderImpl mailSender = new JavaMailSenderImpl();
// 如果為普通郵箱, 非ssl認證等
mailSender.setHost(env.getProperty("mail.host").trim());
mailSender.setPort(Integer.parseInt(env.getProperty("mail.port").trim()));
mailSender.setUsername(env.getProperty("mail.username").trim());
mailSender.setPassword(env.getProperty("mail.password").trim());
mailSender.setDefaultEncoding("utf-8");
// 配置郵件服務器
Properties props = new Properties();
// 讓服務器進行認證,認證用戶名和密碼是否正確
props.put("mail.smtp.auth", "true");
props.put("mail.smtp.timeout", "25000");
mailSender.setJavaMailProperties(props);
return mailSender;
}
}
~~~
這些配置信息,我們在配置文件 mail.properties 中維護。
~~~null
mail.host=smtp.163.com
mail.port=25
mail.username=用戶名
mail.password=密碼
~~~
最后,我們寫一個 main 方法,將 Spring Boot 服務運行起來吧。
至此,我們也完成了一個消息消費者的工程,它將不斷地從消息隊列中處理郵件消息。
## 源代碼
> 相關示例完整代碼:[https://github.com/lianggzone/rabbitmq-server](https://github.com/lianggzone/rabbitmq-server)
- 一.JVM
- 1.1 java代碼是怎么運行的
- 1.2 JVM的內存區域
- 1.3 JVM運行時內存
- 1.4 JVM內存分配策略
- 1.5 JVM類加載機制與對象的生命周期
- 1.6 常用的垃圾回收算法
- 1.7 JVM垃圾收集器
- 1.8 CMS垃圾收集器
- 1.9 G1垃圾收集器
- 2.面試相關文章
- 2.1 可能是把Java內存區域講得最清楚的一篇文章
- 2.0 GC調優參數
- 2.1GC排查系列
- 2.2 內存泄漏和內存溢出
- 2.2.3 深入理解JVM-hotspot虛擬機對象探秘
- 1.10 并發的可達性分析相關問題
- 二.Java集合架構
- 1.ArrayList深入源碼分析
- 2.Vector深入源碼分析
- 3.LinkedList深入源碼分析
- 4.HashMap深入源碼分析
- 5.ConcurrentHashMap深入源碼分析
- 6.HashSet,LinkedHashSet 和 LinkedHashMap
- 7.容器中的設計模式
- 8.集合架構之面試指南
- 9.TreeSet和TreeMap
- 三.Java基礎
- 1.基礎概念
- 1.1 Java程序初始化的順序是怎么樣的
- 1.2 Java和C++的區別
- 1.3 反射
- 1.4 注解
- 1.5 泛型
- 1.6 字節與字符的區別以及訪問修飾符
- 1.7 深拷貝與淺拷貝
- 1.8 字符串常量池
- 2.面向對象
- 3.關鍵字
- 4.基本數據類型與運算
- 5.字符串與數組
- 6.異常處理
- 7.Object 通用方法
- 8.Java8
- 8.1 Java 8 Tutorial
- 8.2 Java 8 數據流(Stream)
- 8.3 Java 8 并發教程:線程和執行器
- 8.4 Java 8 并發教程:同步和鎖
- 8.5 Java 8 并發教程:原子變量和 ConcurrentMap
- 8.6 Java 8 API 示例:字符串、數值、算術和文件
- 8.7 在 Java 8 中避免 Null 檢查
- 8.8 使用 Intellij IDEA 解決 Java 8 的數據流問題
- 四.Java 并發編程
- 1.線程的實現/創建
- 2.線程生命周期/狀態轉換
- 3.線程池
- 4.線程中的協作、中斷
- 5.Java鎖
- 5.1 樂觀鎖、悲觀鎖和自旋鎖
- 5.2 Synchronized
- 5.3 ReentrantLock
- 5.4 公平鎖和非公平鎖
- 5.3.1 說說ReentrantLock的實現原理,以及ReentrantLock的核心源碼是如何實現的?
- 5.5 鎖優化和升級
- 6.多線程的上下文切換
- 7.死鎖的產生和解決
- 8.J.U.C(java.util.concurrent)
- 0.簡化版(快速復習用)
- 9.鎖優化
- 10.Java 內存模型(JMM)
- 11.ThreadLocal詳解
- 12 CAS
- 13.AQS
- 0.ArrayBlockingQueue和LinkedBlockingQueue的實現原理
- 1.DelayQueue的實現原理
- 14.Thread.join()實現原理
- 15.PriorityQueue 的特性和原理
- 16.CyclicBarrier的實際使用場景
- 五.Java I/O NIO
- 1.I/O模型簡述
- 2.Java NIO之緩沖區
- 3.JAVA NIO之文件通道
- 4.Java NIO之套接字通道
- 5.Java NIO之選擇器
- 6.基于 Java NIO 實現簡單的 HTTP 服務器
- 7.BIO-NIO-AIO
- 8.netty(一)
- 9.NIO面試題
- 六.Java設計模式
- 1.單例模式
- 2.策略模式
- 3.模板方法
- 4.適配器模式
- 5.簡單工廠
- 6.門面模式
- 7.代理模式
- 七.數據結構和算法
- 1.什么是紅黑樹
- 2.二叉樹
- 2.1 二叉樹的前序、中序、后序遍歷
- 3.排序算法匯總
- 4.java實現鏈表及鏈表的重用操作
- 4.1算法題-鏈表反轉
- 5.圖的概述
- 6.常見的幾道字符串算法題
- 7.幾道常見的鏈表算法題
- 8.leetcode常見算法題1
- 9.LRU緩存策略
- 10.二進制及位運算
- 10.1.二進制和十進制轉換
- 10.2.位運算
- 11.常見鏈表算法題
- 12.算法好文推薦
- 13.跳表
- 八.Spring 全家桶
- 1.Spring IOC
- 2.Spring AOP
- 3.Spring 事務管理
- 4.SpringMVC 運行流程和手動實現
- 0.Spring 核心技術
- 5.spring如何解決循環依賴問題
- 6.springboot自動裝配原理
- 7.Spring中的循環依賴解決機制中,為什么要三級緩存,用二級緩存不夠嗎
- 8.beanFactory和factoryBean有什么區別
- 九.數據庫
- 1.mybatis
- 1.1 MyBatis-# 與 $ 區別以及 sql 預編譯
- Mybatis系列1-Configuration
- Mybatis系列2-SQL執行過程
- Mybatis系列3-之SqlSession
- Mybatis系列4-之Executor
- Mybatis系列5-StatementHandler
- Mybatis系列6-MappedStatement
- Mybatis系列7-參數設置揭秘(ParameterHandler)
- Mybatis系列8-緩存機制
- 2.淺談聚簇索引和非聚簇索引的區別
- 3.mysql 證明為什么用limit時,offset很大會影響性能
- 4.MySQL中的索引
- 5.數據庫索引2
- 6.面試題收集
- 7.MySQL行鎖、表鎖、間隙鎖詳解
- 8.數據庫MVCC詳解
- 9.一條SQL查詢語句是如何執行的
- 10.MySQL 的 crash-safe 原理解析
- 11.MySQL 性能優化神器 Explain 使用分析
- 12.mysql中,一條update語句執行的過程是怎么樣的?期間用到了mysql的哪些log,分別有什么作用
- 十.Redis
- 0.快速復習回顧Redis
- 1.通俗易懂的Redis數據結構基礎教程
- 2.分布式鎖(一)
- 3.分布式鎖(二)
- 4.延時隊列
- 5.位圖Bitmaps
- 6.Bitmaps(位圖)的使用
- 7.Scan
- 8.redis緩存雪崩、緩存擊穿、緩存穿透
- 9.Redis為什么是單線程、及高并發快的3大原因詳解
- 10.布隆過濾器你值得擁有的開發利器
- 11.Redis哨兵、復制、集群的設計原理與區別
- 12.redis的IO多路復用
- 13.相關redis面試題
- 14.redis集群
- 十一.中間件
- 1.RabbitMQ
- 1.1 RabbitMQ實戰,hello world
- 1.2 RabbitMQ 實戰,工作隊列
- 1.3 RabbitMQ 實戰, 發布訂閱
- 1.4 RabbitMQ 實戰,路由
- 1.5 RabbitMQ 實戰,主題
- 1.6 Spring AMQP 的 AMQP 抽象
- 1.7 Spring AMQP 實戰 – 整合 RabbitMQ 發送郵件
- 1.8 RabbitMQ 的消息持久化與 Spring AMQP 的實現剖析
- 1.9 RabbitMQ必備核心知識
- 2.RocketMQ 的幾個簡單問題與答案
- 2.Kafka
- 2.1 kafka 基礎概念和術語
- 2.2 Kafka的重平衡(Rebalance)
- 2.3.kafka日志機制
- 2.4 kafka是pull還是push的方式傳遞消息的?
- 2.5 Kafka的數據處理流程
- 2.6 Kafka的腦裂預防和處理機制
- 2.7 Kafka中partition副本的Leader選舉機制
- 2.8 如果Leader掛了的時候,follower沒來得及同步,是否會出現數據不一致
- 2.9 kafka的partition副本是否會出現腦裂情況
- 十二.Zookeeper
- 0.什么是Zookeeper(漫畫)
- 1.使用docker安裝Zookeeper偽集群
- 3.ZooKeeper-Plus
- 4.zk實現分布式鎖
- 5.ZooKeeper之Watcher機制
- 6.Zookeeper之選舉及數據一致性
- 十三.計算機網絡
- 1.進制轉換:二進制、八進制、十六進制、十進制之間的轉換
- 2.位運算
- 3.計算機網絡面試題匯總1
- 十四.Docker
- 100.面試題收集合集
- 1.美團面試常見問題總結
- 2.b站部分面試題
- 3.比心面試題
- 4.騰訊面試題
- 5.哈羅部分面試
- 6.筆記
- 十五.Storm
- 1.Storm和流處理簡介
- 2.Storm 核心概念詳解
- 3.Storm 單機版本環境搭建
- 4.Storm 集群環境搭建
- 5.Storm 編程模型詳解
- 6.Storm 項目三種打包方式對比分析
- 7.Storm 集成 Redis 詳解
- 8.Storm 集成 HDFS 和 HBase
- 9.Storm 集成 Kafka
- 十六.Elasticsearch
- 1.初識ElasticSearch
- 2.文檔基本CRUD、集群健康檢查
- 3.shard&replica
- 4.document核心元數據解析及ES的并發控制
- 5.document的批量操作及數據路由原理
- 6.倒排索引
- 十七.分布式相關
- 1.分布式事務解決方案一網打盡
- 2.關于xxx怎么保證高可用的問題
- 3.一致性hash原理與實現
- 4.微服務注冊中心 Nacos 比 Eureka的優勢
- 5.Raft 協議算法
- 6.為什么微服務架構中需要網關
- 0.CAP與BASE理論
- 十八.Dubbo
- 1.快速掌握Dubbo常規應用
- 2.Dubbo應用進階
- 3.Dubbo調用模塊詳解
- 4.Dubbo調用模塊源碼分析
- 6.Dubbo協議模塊