一、 緩存
1)JSR107
Java Caching定義了5個核心接口,分別是:
- CachingProvider
定義了創(chuàng)建、配置、獲取、管理和控制多個CacheManager。一個應用可以在運行期訪問多個CachingProvider。
- CacheManager
定義了創(chuàng)建、配置、獲取、管理和控制多個唯一命名的Cache,這些Cache存在于CacheManager的上下文中。一個CacheManager僅被一個CachingProvider所擁有。
- Cache
是一個類似Map的數(shù)據(jù)結(jié)構(gòu)并臨時存儲以Key為索引的值。一個Cache僅被一個CacheManager所擁有。
- Entry
是一個存儲在Cache中的key-value對。
- Expiry
每一個存儲在Cache中的條目有一個定義的有效期。一旦超過這個時間,條目為過期的狀態(tài)。一旦過期,條目將不可訪問、更新和刪除。緩存有效期可以通過ExpiryPolicy設置。

2)Spring緩存抽象
Spring從3.1開始定義了
org.springframework.cache.Cache和org.springframework.cache.CacheManager接口來統(tǒng)一不同的緩存技術(shù),并支持使用JCache(JSR-107)注解簡化我們開發(fā)。
- Cache接口為緩存的組件規(guī)范定義,包含緩存的各種操作集合
- Cache接口下Spring提供了各種xxxCache的實現(xiàn);如RedisCache,EhCacheCache ,ConcurrentMapCache等
- 每次調(diào)用需要緩存功能的方法時,Spring會檢查指定參數(shù)的指定的目標方法是否已經(jīng)被調(diào)用過,如果有就直接從緩存中獲取方法調(diào)用后的結(jié)果,如果沒有就調(diào)用方法并緩存結(jié)果后返回給用戶。下次調(diào)用直接從緩存中獲取。
- 使用Spring緩存抽象時我們需要關(guān)注以下兩點:
- 確定方法需要被緩存以及他們的緩存策略
- 從緩存中讀取之前緩存存儲的數(shù)據(jù)

3)幾個重要緩存注解



4)緩存使用
- 引入spring-boot-starter-cache模塊
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
復制代碼
- 利用@EnableCaching開啟緩存
@MapperScan("cbuc.life.cache.mapper")
@SpringBootApplication
@EnableCaching
public class Springboot01CacheApplication {
public static void main(String[] args) {
SpringApplication.run(Springboot01CacheApplication.class, args);
}
}
復制代碼
- 注解使用
- @Cacheable
- @CacheEvict
- @CachePut
將數(shù)據(jù)保存在ConcurrentMap<Object, Object>中
開發(fā)中使用緩存中間件:redis、memcached、ehcache
5)幾大屬性
- cacheNames/value: 指定緩存組件的名字,將方法的返回結(jié)果放在哪個緩存中,是數(shù)組的方式,可以指定多個緩存
- key: 緩存數(shù)據(jù)使用的key,可以用它來指定。默認是使用方法參數(shù)的值
- keyGenerator: key的生成器,可以自己指定key的生成器的組件id
- cacheManager:指定緩存管理器,或者 cacheResolver 指定獲取解析器
- condition:指定符合條件的情況下才緩存
- unless:否定緩存,當 unless 指定的條件為true,方法的返回值就不會被緩存
- sync:是否使用異步模式
- 示例1)


2)

3)

4)

6)整合redis
- 引入spring-boot-starter-data-redis
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
復制代碼
2.application.yml配置 redis 連接地址
spring.redis.host=118.24.44.169
復制代碼
3.使用ReditTemplate操作redis

4.配置緩存

5.自定義redisTemplate

6.將自定義的redisTemplate注冊進RedisCacheManager

二、消息
*大多應用中,可通過消息服務中間件來提升系統(tǒng)異步通信、擴展解耦能力 *
消息服務中兩個重要概念:
- 消息代理(message broker)
- 目的地(destination)
當消息發(fā)送者發(fā)送消息以后,將由消息代理接管,消息代理保證消息傳遞到指定目的地。
消息隊列主要有兩種形式的目的地
- 隊列(queue):點對點消息通信(point-to-point)
- 主題(topic):發(fā)布(publish)/訂閱(subscribe)消息通信
點對點式
- 消息發(fā)送者發(fā)送消息,消息代理將其放入一個隊列中,消息接收者從隊列中獲取消息內(nèi)容,消息讀取后被移出隊列
- 消息只有唯一的發(fā)送者和接受者,但并不是說只能有一個接收者
發(fā)布訂閱式
- 發(fā)送者(發(fā)布者)發(fā)送消息到主題,多個接收者(訂閱者)監(jiān)聽(訂閱)這個主題,那么就會在消息到達時同時收到消息
JMS(Java Message Service)JAVA消息服務
- 基于JVM消息代理的規(guī)范。ActiveMQ、HornetMQ是JMS實現(xiàn)
AMQP(Advanced Message Queuing Protocol)
- 高級消息隊列協(xié)議,也是一個消息代理的規(guī)范,兼容JMS
- RabbitMQ是AMQP的實現(xiàn)

Spring支持
- spring-jms提供了對JMS的支持
- spring-rabbit提供了對AMQP的支持
- 需要ConnectionFactory的實現(xiàn)來連接消息代理
- 提供JmsTemplate、RabbitTemplate來發(fā)送消息
- @JmsListener(JMS)、@RabbitListener(AMQP)注解在方法上監(jiān)聽消息代理發(fā)布的消息
- @EnableJms、@EnableRabbit開啟支持
Spring Boot自動配置
- JmsAutoConfiguration
- RabbitAutoConfiguration
RabbitMQ
簡介:
RabbitMQ是一個由erlang開發(fā)的AMQP(Advanved Message Queue Protocol)的開源實現(xiàn)。
核心概念:

-
Message:
消息,消息是不具名的,它由消息頭和消息體組成。消息體是不透明的,而消息頭則由一系列的可選屬性組成,這些屬性包括routing-key(路由鍵)、priority(相對于其他消息的優(yōu)先權(quán))、delivery-mode(指出該消息可能需要持久性存儲)等。 -
Publisher:
消息的生產(chǎn)者,也是一個向交換器發(fā)布消息的客戶端應用程序。 -
Consumer:
消息的消費者,表示一個從消息隊列中取得消息的客戶端應用程序。 -
Exchange:
交換器,用來接收生產(chǎn)者發(fā)送的消息并將這些消息路由給服務器中的隊列。不同類型的Exchange轉(zhuǎn)發(fā)消息的策略有所區(qū)別 - direct(默認)
- fanout
- topic
- headers
-
Queue:
消息隊列,用來保存消息直到發(fā)送給消費者。它是消息的容器,也是消息的終點。一個消息可投入一個或多個隊列。消息一直在隊列里面,等待消費者連接到這個隊列將其取走。 -
Binding:
綁定,用于消息隊列和交換器之間的關(guān)聯(lián)。一個綁定就是基于路由鍵將交換器和消息隊列連接起來的路由規(guī)則,所以可以將交換器理解成一個由綁定構(gòu)成的路由表。Exchange 和Queue的綁定可以是多對多的關(guān)系。 -
Connection:
網(wǎng)絡連接,比如一個TCP連接。 -
Channel:
信道,多路復用連接中的一條獨立的雙向數(shù)據(jù)流通道。信道是建立在真實的TCP連接內(nèi)的虛擬連接,AMQP 命令都是通過信道發(fā)出去的,不管是發(fā)布消息、訂閱隊列還是接收消息,這些動作都是通過信道完成。因為對于操作系統(tǒng)來說建立和銷毀 TCP 都是非常昂貴的開銷,所以引入了信道的概念,以復用一條 TCP 連接。 -
Virtual Host:
虛擬主機,表示一批交換器、消息隊列和相關(guān)對象。虛擬主機是共享相同的身份認證和加密環(huán)境的獨立服務器域。每個 vhost 本質(zhì)上就是一個 mini 版的 RabbitMQ 服務器,擁有自己的隊列、交換器、綁定和權(quán)限機制。vhost 是 AMQP 概念的基礎(chǔ),必須在連接時指定,RabbitMQ 默認的 vhost 是 / 。 -
Broker:
表示消息隊列服務器實體
運行機制:
- AMQP 中的消息路由
AMQP 中消息的路由過程和 Java 開發(fā)者熟悉的 JMS 存在一些差別,AMQP 中增加了 Exchange 和Binding 的角色。生產(chǎn)者把消息發(fā)布到 Exchange 上,消息最終到達隊列并被消費者接收,而 Binding 決定交換器的消息應該發(fā)送到哪個隊列。

- Exchange 類型
Exchange分發(fā)消息時根據(jù)類型的不同分發(fā)策略有區(qū)別,目前共四種類型:direct、fanout、topic、headers 。headers 匹配 AMQP 消息的* header 而不是路由鍵*, headers 交換器和 direct 交換器完全一致,但性能差很多,目前幾乎用不到了,所以直接看另外三種類型:
- direct

消息中的路由鍵(routing key)如果和 Binding 中的 binding key 一致, 交換器就將消息發(fā)到對應的隊列中。路由鍵與隊列名完全匹配,如果一個隊列綁定到交換機要求路由鍵為“dog”,則只轉(zhuǎn)發(fā) routing key 標記為“dog”的消息,不會轉(zhuǎn)發(fā)“dog.puppy”,也不會轉(zhuǎn)發(fā)“dog.guard”等等。它是完全匹配、單播的模式。
2.fanout

每個發(fā)到 fanout 類型交換器的消息都會分到所有綁定的隊列上去。fanout 交換器不處理路由鍵,只是簡單的將隊列綁定到交換器上,每個發(fā)送到交換器的消息都會被轉(zhuǎn)發(fā)到與該交換器綁定的所有隊列上。很像子網(wǎng)廣播,每臺子網(wǎng)內(nèi)的主機都獲得了一份復制的消息。fanout 類型轉(zhuǎn)發(fā)消息是最快的。
3.topic

topic 交換器通過模式匹配分配消息的路由鍵屬性,將路由鍵和某個模式進行匹配,此時隊列需要綁定到一個模式上。它將路由鍵和綁定鍵的字符串切分成單詞,這些單詞之間用點隔開。它同樣也會識別兩個通配符:符號“#”和符號“”。#匹配0個或多個單詞,匹配一個單詞。
整合RabbitMQ
- 引入 spring-boot-starter-amqp
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
復制代碼
- application.yml配置
spring.rabbitmq.host=118.24.44.169
spring.rabbitmq.username=guest
spring.rabbitmq.password=guest
復制代碼
- 啟動類

示例:
- 自定義消息轉(zhuǎn)換器,以Jackson的形式轉(zhuǎn)換
@Configuration
public class MyAMQPConfig {
@Bean
public MessageConverter messageConverter(){
return new Jackson2JsonMessageConverter();
}
}
復制代碼
- 注入Bean
/**
* 注入我們需要的兩個bean
*/
@Autowired
RabbitTemplate rabbitTemplate;
@Autowired
AmqpAdmin amqpAdmin;
復制代碼
- 不同模式發(fā)送

Service 中接收消息示例
注意要在啟動類中開啟基于注解的RabbitMQ模式 :@EnableRabbit

三、檢索
我們的應用經(jīng)常需要添加檢索功能,開源的 ElasticSearch是目前全文搜索引擎的首選。他可以快速的存儲、搜索和分析海量數(shù)據(jù)。Spring Boot通過整合Spring Data ElasticSearch為我們提供了非常便捷的檢索功能支持;
Elasticsearch是一個分布式搜索服務,提供Restful API,底層基于Lucene,采用多shard(分片)的方式保證數(shù)據(jù)安全,并且提供自動resharding的功能,github等大型的站點也是采用了ElasticSearch作為其搜索服務
概念
以 員工文檔 的形式存儲為例:一個文檔代表一個員工數(shù)據(jù)。存儲數(shù)據(jù)到 ElasticSearch 的行為叫做索引,但在索引一個文檔之前,需要確定將文檔存儲在哪里。
一個 ElasticSearch 集群可以 包含多個 索引,相應的每個索引可以包含多個 類型 。 這些不同的類型存儲著多個 文檔 ,每個文檔又有 多個 屬性 。
類似關(guān)系:
- 索引 ==== 數(shù)據(jù)庫
- 類型 ==== 表
- 文檔 ==== 表中的記錄
- 屬性 ==== 列

整合ElasticSearch
SpringBoot 默認支持兩種技術(shù)來和 ES 交互
- Jest(默認不生效)
導入jest的工具包
<dependency>
<groupId>io.searchbox</groupId>
<artifactId>jest</artifactId>
<version>5.3.3</version>
</dependency>
復制代碼
- SpringData ElasticSearch
- 版本適配問題升級SpringBoot版本安裝對應版本的ES
- 導入工具包
<!--SpringBoot默認使用SpringData ElasticSearch模塊進行操作-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
復制代碼
- 安裝Spring Data 對應版本的ElasticSearch
- application.properties配置
spring.data.elasticsearch.cluster-name=elasticsearch
spring.data.elasticsearch.cluster-nodes=118.24.44.169:9301
復制代碼
- 測試
- 注入Bean:
@Document(indexName = "cbuc",type = "book")
public class Book {
private Integer id;
private String bookName;
private String author;
//省略 get/set方法
}
public class Article {
@JestId
private Integer id;
private String author;
private String title;
private String content;
//省略 get/set 方法
}
復制代碼
- 編寫 ElasticsearchRepository:
public interface BookRepository extends ElasticsearchRepository<Book,Integer> {
//參照 https://docs.spring.io/spring-data/elasticsearch/docs/3.0.6.RELEASE/reference/html/
public List<Book> findByBookNameLike(String bookName);
}
復制代碼
- 引入所需要的Bean:
@Autowired
JestClient jestClient;
@Autowired
BookRepository bookRepository;
復制代碼
法1:
存入index:
Book book = new Book();
book.setId(1);
book.setBookName("西游記");
book.setAuthor("吳承恩");
bookRepository.index(book);
復制代碼
?。?/p>
for (Book book : bookRepository.findByBookNameLike("游")) {
System.out.println(book);
}
復制代碼
法2:
//給Es中索引(保存)一個文檔:
Article article = new Article();
article.setId(1);
article.setTitle("測試");
article.setAuthor("cbuc");
article.setContent("測試檢索");
//構(gòu)建一個索引功能
Index index = new Index.Builder(article).index("cbuc").type("news").build();
//執(zhí)行
jestClient.execute(index);
復制代碼
測試搜索:
//查詢表達式
@Test
public void search(){
//查詢表達式
String json ="{\n" +
" \"query\" : {\n" +
" \"match\" : {\n" +
" \"content\" : \"hello\"\n" +
" }\n" +
" }\n" +
"}";
//更多操作: https://github.com/searchbox-io/Jest/tree/master/jest
//構(gòu)建搜索功能
Search search = new Search.Builder(json).addIndex("cbuc").addType("news").build();
//執(zhí)行
try {
SearchResult result = jestClient.execute(search);
System.out.println(result.getJsonString());
} catch (IOException e) {
e.printStackTrace();
}
}
復制代碼
四、任務
異步任務
在Java應用中,絕大多數(shù)情況下都是通過同步的方式來實現(xiàn)交互處理的。但是在處理與第三方系統(tǒng)交互的時候,容易造成響應遲緩的情況,之前大部分都是使用多線程來完成此類任務,其實,在Spring 3.x之后,就已經(jīng)內(nèi)置了@Async來完美解決這個問題。
兩個注解:
- @EnableAysnc
- @Aysnc
使用:
- 啟動類開啟異步注解功能
@EnableAsync //開啟異步注解功能
@SpringBootApplication
public class Springboot04TaskApplication {
public static void main(String[] args) {
SpringApplication.run(Springboot04TaskApplication.class, args);
}
}
復制代碼
2.Service:
@Service
public class AsyncService {
//告訴Spring這是一個異步方法
@Async
public void test(){
try {
//當前線程睡眠 3 秒頁面也能及時響應
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("處理數(shù)據(jù)中...");
}
}
復制代碼
3.Controller:
@RestController
public class AsyncController {
@Autowired
AsyncService asyncService;
@GetMapping("/hello")
public String hello(){
asyncService.test();
return "success";
}
}
復制代碼
定時任務
項目開發(fā)中經(jīng)常需要執(zhí)行一些定時任務,比如需要在每天凌晨時候,分析一次前一天的日志信息。Spring為我們提供了異步執(zhí)行任務調(diào)度的方式,提供TaskExecutor、TaskScheduler接口。
兩個注解:
- @EnableScheduling
- @Scheduled
cron表達式:


使用:
- 啟動類開啟基于注解的定時任務:
@EnableScheduling //開啟基于注解的定時任務
@SpringBootApplication
public class Springboot04TaskApplication {
public static void main(String[] args) {
SpringApplication.run(Springboot04TaskApplication.class, args);
}
}
復制代碼
2.Service:

郵件任務
使用:
- 郵件發(fā)送需要引入spring-boot-starter-mail
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>
復制代碼
- 定義MailProperties內(nèi)容,配置在application.properties中
spring.mail.username=(發(fā)送人qq賬號)
spring.mail.password=(發(fā)送人qq密碼)
spring.mail.host=smtp.qq.com
spring.mail.properties.mail.smtp.ssl.enable=true
復制代碼
3.自動裝配JavaMailSender

五、分布式系統(tǒng)
單一應用架構(gòu)
當網(wǎng)站流量很小時,只需一個應用,將所有功能都部署在一起,以減少部署節(jié)點和成本。此時,用于簡化增刪改查工作量的數(shù)據(jù)訪問框架(ORM)是關(guān)鍵。
垂直應用架構(gòu)
當訪問量逐漸增大,單一應用增加機器帶來的加速度越來越小,將應用拆成互不相干的幾個應用,以提升效率。此時,用于加速前端頁面開發(fā)的Web框架(MVC)是關(guān)鍵。
流動計算架構(gòu)
當服務越來越多,容量的評估,小服務資源的浪費等問題逐漸顯現(xiàn),此時需增加一個調(diào)度中心基于訪問壓力實時管理集群容量,提高集群利用率。此時,用于提高機器利用率的資源調(diào)度和治理中心(SOA)是關(guān)鍵
分布式服務架構(gòu)
當垂直應用越來越多,應用之間交互不可避免,將核心業(yè)務抽取出來,作為獨立的服務,逐漸形成穩(wěn)定的服務中心,使前端應用能更快速的響應多變的市場需求。此時,用于提高業(yè)務復用及整合的分布式服務框架(RPC)是關(guān)鍵。
Zookeeper和Dubbo
ZooKeeper:
是一個分布式的,開放源碼的分布式應用程序協(xié)調(diào)服務。它是一個為分布式應用提供一致性服務的軟件,提供的功能包括:配置維護、域名服務、分布式同步、組服務等。
Dubbo:
是Alibaba開源的分布式服務框架,它最大的特點是按照分層的方式來架構(gòu),使用這種方式可以使各個層之間解耦合(或者最大限度地松耦合)。從服務模型的角度來看,Dubbo采用的是一種非常簡單的模型,要么是提供方提供服務,要么是消費方消費服務,所以基于這一點可以抽象出服務提供方(Provider)和服務消費方(Consumer)兩個角色。

使用:
- 安裝zookeeper作為注冊中心
- 編寫服務提供者
引入dubbo和zkclient相關(guān)依賴
<dependency>
<groupId>com.alibaba.boot</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
<version>0.1.0</version>
</dependency>
<!--引入zookeeper的客戶端工具-->
<!-- https://mvnrepository.com/artifact/com.github.sgroschupf/zkclient -->
<dependency>
<groupId>com.github.sgroschupf</groupId>
<artifactId>zkclient</artifactId>
<version>0.1</version>
</dependency>
復制代碼
配置dubbo的掃描包和注冊中心地址
dubbo.application.name=provider-ticket
dubbo.registry.address=zookeeper://118.24.44.169:2181
dubbo.scan.base-packages=cbuc.life.ticket.service
復制代碼
使用@Service發(fā)布服務
@Component
@Service //將服務發(fā)布出去 注意注解 Service是dubbo包下的
public class TicketServiceImpl implements TicketService {
@Override
public String getTicket() {
return "《我和我的祖國》";
}
}
復制代碼
- 編寫服務消費者
引入dubbo和zkclient相關(guān)依賴
<dependency>
<groupId>com.alibaba.boot</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
<version>0.1.0</version>
</dependency>
<!--引入zookeeper的客戶端工具-->
<!-- https://mvnrepository.com/artifact/com.github.sgroschupf/zkclient -->
<dependency>
<groupId>com.github.sgroschupf</groupId>
<artifactId>zkclient</artifactId>
<version>0.1</version>
</dependency>
復制代碼
引用服務
@Service
public class UserService{
@Reference // 使用 Reference 引入服務
TicketService ticketService;
public void hello(){
String ticket = ticketService.getTicket();
System.out.println("調(diào)用買票服務:"+ticket);
}
}
復制代碼
Spring Boot和Spring Cloud
Spring Cloud是一個分布式的整體解決方案。Spring Cloud 為開發(fā)者提供了在分布式系統(tǒng)(配置管理,服務發(fā)現(xiàn),熔斷,路由,微代理,控制總線,一次性token,全局鎖,leader選舉,分布式session,集群狀態(tài))中快速構(gòu)建的工具,使用Spring Cloud的開發(fā)者可以快速的啟動服務或構(gòu)建應用、同時能夠快速和云平臺資源進行對接。
SpringCloud分布式開發(fā)五大常用組件:
- 服務發(fā)現(xiàn) —— Netflix Eureka
- 客服端負載均衡 —— Netflix Ribbon
- 斷路器 —— Netflix Hystrix
- 服務網(wǎng)關(guān) —— Netflix Zuul
- 分布式配置 —— Spring Cloud Config
使用:
- 引入Eureka注冊中心
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Edgware.SR3</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
復制代碼
- 配置application
server:
port: 8761
eureka:
instance:
hostname: eureka-server # eureka實例的主機名
client:
register-with-eureka: false #不把自己注冊到eureka上
fetch-registry: false #不從eureka上來獲取服務的注冊信息
service-url:
defaultZone: http://localhost:8761/eureka/
復制代碼
- 啟動類:
@EnableEurekaServer
@SpringBootApplication
public class EurekaServerApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaServerApplication.class, args);
}
}
復制代碼
- 創(chuàng)建provider
引入依賴

配置application

啟動類

service

controller

- 創(chuàng)建consumer
引入依賴

配置application

啟動類

controller (通過使用RestTemplate調(diào)用服務)
