date: 2017-12-18 13:28:54
title: 從 spring 中學(xué)習(xí) -- D大教程學(xué)習(xí)筆記
description: swoft 一開始就有 spring cloud 這樣的野心, 雖道阻且長, 吾輩亦當(dāng)要上下求索
spring -> spring boot -> spring cloud
spring project: https://github.com/spring-projects
spring 教程: https://github.com/dyc87112
參與 swoft 開源項(xiàng)目 的開發(fā)工作, 開發(fā)組技術(shù)選型上比較傾向于 spring 的設(shè)計(jì), 我在一塊還比較薄弱, 所以開了這篇來補(bǔ)一補(bǔ). 也許是給自己挖了一個(gè)巨大的坑, 不過我一向 樂天派 -- 萬水千山只等閑, 且看今朝.
系列教程編寫指南: 主題講解 -> 代碼示例 + 單元測試
spring boot
最為核心的兩大要素: 依賴注入DI + 面向切面編程AOP
- 類 -> bean; 配置 -> 類定義/類屬性
- 模板引擎: 靜態(tài)資源位置 屬性(數(shù)據(jù))解析 默認(rèn)參數(shù)配置
- apidoc - swagger
- 默認(rèn)錯(cuò)誤頁面: 統(tǒng)一異常處理 + error page/json
- security 安全控制: AOP/攔截器 是否需要登錄
- 數(shù)據(jù)庫訪問: JdbcTemplate
- Spring-data-jpa: Hibernate Entity->Repository 不同DB連接
- spring-data-redis: 存儲對象 + 對象序列化接口
- spring-data-mongodb: Entity->Repository 配置->連接池
- mybatis-spring-boot-starter: Entity->Mapper
- Flyway: 數(shù)據(jù)庫版本控制 migration
- spring-data-ldap: 輕量級目錄訪問協(xié)議 -> 用戶管理體系
- cache: cache vs buffer; SpEL CacheManager 緩存生命周期的控制
- log: logger(commonLogging log4j logback) logFormat(時(shí)間/毫秒 日志級別/多環(huán)境/動態(tài)修改 進(jìn)程id 分隔符 logger名 日志內(nèi)容) logTarget(console/多彩 file-分類輸出/package mongo)
- AOP: 日志切面 同步問題/記錄函數(shù)執(zhí)行時(shí)間/ThreadLocal 優(yōu)先級問題/同一切入點(diǎn)多個(gè)切面
- mq: sender -> 隊(duì)列/交換器/路由 -> receiver
- task: 同步 異步/異步回調(diào) 線程池 優(yōu)雅關(guān)閉(平滑重啟) Future->Runnable/Callable->任務(wù)取消/是否完成/獲取結(jié)果->阻塞
- 郵件: 附件 靜態(tài)資源 模板
- Actuator: 應(yīng)用配置(配置刷新) 度量指標(biāo) 操作控制
- cli StateMachine(state->event)
// 從注解中獲取屬性
@Value("${com.didispace.blog.name}")
private String name;
// web
@RestController() // 不需要 @ResponseBody
@Controller()
@ResponseBody()
@RequestMapping()
@PathVariable
@RequestParam
@ModelAttribute
// Swagger2
@ApiOperation(value="更新用戶詳細(xì)信息", notes="根據(jù)url的id來指定更新對象赠潦,并根據(jù)傳過來的user信息來更新用戶詳細(xì)信息")
@ApiImplicitParams({
@ApiImplicitParam(name = "id", value = "用戶ID", required = true, dataType = "Long"),
@ApiImplicitParam(name = "user", value = "用戶詳細(xì)實(shí)體user", required = true, dataType = "User")
})
// Spring-data-jpa
@Query("from User u where u.name=:name")
User findUser(@Param("name") String name);
// mybatis-spring-boot-starter
@Select("SELECT * FROM USER WHERE NAME = #{name}")
User findByName(@Param("name") String name);
@Insert("INSERT INTO USER(NAME, AGE) VALUES(#{name}, #{age})")
int insert(@Param("name") String name, @Param("age") Integer age);
// 數(shù)據(jù)庫事務(wù)
@Transactional(isolation = Isolation.DEFAULT, propagation = Propagation.REQUIRED)
@Rollback
@EnableCaching // application
@Cacheable // repository
@CachePut // 緩存更新
// task
@EnableScheduling
@Scheduled(fixedRate = 5000) // 5s
@Scheduled(fixedDelay = 5000)
@Scheduled(initialDelay=1000, fixedRate=5000)
@Scheduled(cron="*/5 * * * * *")
@EnableAsync
@Async
// 等待任務(wù)全部執(zhí)行完
while(true) {
if(task1.isDone() && task2.isDone() && task3.isDone()) {
// 三個(gè)任務(wù)都調(diào)用完成,退出循環(huán)等待
break;
}
Thread.sleep(1000);
}
// task 線程池
@Async("taskExecutor")
@Bean("taskExecutor")
public Executor taskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(10);
executor.setMaxPoolSize(20);
executor.setQueueCapacity(200);
executor.setKeepAliveSeconds(60);
executor.setThreadNamePrefix("taskExecutor-");
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
// 優(yōu)雅關(guān)閉
executor.setWaitForTasksToCompleteOnShutdown(true);
executor.setAwaitTerminationSeconds(60);
return executor;
}
@Test
# 參數(shù)引用
com.didispace.blog.desc=${com.didispace.blog.name}正在努力寫《${com.didispace.blog.title}》
# 隨機(jī)數(shù)
com.didispace.blog.value=${random.value}
# 命令行設(shè)置屬性
java -jar xxx.jar --server.port=8888
# 多環(huán)境
spring.profiles.active=test
application-dev.properties # dev/test/prod
# 監(jiān)控
/autoconfig // 配置信息
/beans
/configprops
/env
/mappings
/info
/metrics // 度量指標(biāo)
/health
/dump
/trace
/shutdown // 操作控制
消息隊(duì)列 MQ
message broker:
- 消息路由到 n 個(gè)目的地
- 消息轉(zhuǎn)化為其他形式
- 消息聚集/分解 -> 目的地 -> 重新組合相應(yīng)返回
- 調(diào)用web服務(wù)檢索數(shù)據(jù)
- 響應(yīng)事件/錯(cuò)誤
- pub/sub 或 topic 的消息路由
AMQP:
- 消息方向
- 消息隊(duì)列
- 消息路由(p2p pub/sub)
- 可靠性
- 安全性
狀態(tài)機(jī)
- 狀態(tài)/事件 枚舉
- 狀態(tài)機(jī): 所有狀態(tài) + 初始狀態(tài)
- 狀態(tài)機(jī): 狀態(tài)遷移動作
- 狀態(tài)機(jī): 監(jiān)聽器
spring cloud
高可用: 多節(jié)點(diǎn)/前置LB/注冊為服務(wù)
- 共享資源的互斥訪問 -> 分布式鎖(全局鎖) -> 基于redis 基于Zookeeper 基于Consul的KV存儲實(shí)現(xiàn)分布式鎖/信號量 -> 鎖超時(shí)清理(超時(shí)時(shí)間)
- 限制并發(fā)線程/進(jìn)程數(shù)量(并發(fā)控制) -> 信號量 -> Zuul默認(rèn)情況使用信號量限制每個(gè)路由的并發(fā)數(shù) Consul實(shí)現(xiàn)分布式信號量 -> PV操作
- 服務(wù)注冊發(fā)現(xiàn): Eureka Consul 服務(wù)注冊中心(server)+服務(wù)提供方(client 服務(wù)清單->緩存)+服務(wù)消費(fèi)者(consumer 客戶端負(fù)載均衡)
- 服務(wù)消費(fèi)者: loadBalancerClient Ribbon 輪詢服務(wù)端列表達(dá)到負(fù)載均衡
- 服務(wù)消費(fèi)工具 SC Feign: 聲明式服務(wù)調(diào)用客戶端 可插拔的注解 可插拔的編碼器和解碼器 整合Hystrix來實(shí)現(xiàn)服務(wù)的容錯(cuò)保護(hù) 引入Feign的擴(kuò)展包實(shí)現(xiàn)文件上傳
- 分布式配置中心 SC Config: 服務(wù)端/客戶端 獨(dú)立微服務(wù)應(yīng)用 配置信息/加解密信息(dev->devops 敏感信息) 默認(rèn)采用git來存儲配置信息 配置git.username/git.password->http可訪問 配置刷新->請求客戶端actuator模塊
- 服務(wù)保護(hù)機(jī)制 SC Hystrix: 服務(wù)降級fallback 服務(wù)熔斷/斷路器/快照時(shí)間窗/請求總數(shù)下限/錯(cuò)誤百分比下限 線程隔離/為每一個(gè)依賴服務(wù)都分配一個(gè)線程池 信號量/延遲低/不能設(shè)置超時(shí)和異步訪問 請求緩存 請求合并 服務(wù)監(jiān)控 監(jiān)控面板/turbine(消息聚合 http/mq)
- 服務(wù)網(wǎng)關(guān) SC Zuul: 對外提供服務(wù)/服務(wù)路由/LB/權(quán)限控制/請求限流(過濾器ZuulFilter) 注冊中心->服務(wù)清單->映射 使用Swagger匯總API接口文檔 處理cookie/重定向/異常處理
- 消息驅(qū)動 SC Stream: RabbitMQ/Kafka pub/sub-消費(fèi)組(避免消息重復(fù)消費(fèi))-消息分區(qū)(消息定向投遞, 比如監(jiān)控信息匯總) app->channel->binder(綁定器隱藏中間件細(xì)節(jié), 暴露channel給app)->Middleware(mq中間件)
- 分布式服務(wù)跟蹤 SC Sleuth: 全鏈路調(diào)用跟蹤/快速發(fā)現(xiàn)錯(cuò)誤根源/監(jiān)控分析性能瓶頸 請求鏈路TraceID 基本工作單元SpanID->抽樣Sampler logstash(ELK)日志收集
- 分布式服務(wù)跟蹤整合 Zipkin: 收集器collector 存儲組件Storage Api組件 WebUI http/mq方式收集
// mq
@StreamListener(Sink.INPUT)
@Input(Sink.INPUT)
SubscribableChannel input();
// 實(shí)現(xiàn)命名替換
@Component
spring.application.name=trace-1
server.port=9101
# 服務(wù)注冊中心
eureka.client.serviceUrl.defaultZone=http://eureka.didispace.com/eureka/
# 服務(wù)消費(fèi)者
ribbon.eager-load.enabled=true
ribbon.eager-load.clients=hello-service, user-service
# mq 生產(chǎn)
spring.cloud.stream.bindings.input.group=Service-A # 分組
spring.cloud.stream.bindings.input.destination=greetings
spring.cloud.stream.bindings.input.consumer.partitioned=true # 分區(qū)
spring.cloud.stream.instanceCount=2
spring.cloud.stream.instanceIndex=0
# mq 消費(fèi)
spring.cloud.stream.bindings.output.destination=greetings # 分組
spring.cloud.stream.bindings.output.producer.partitionKeyExpression=payload # 分區(qū)
spring.cloud.stream.bindings.output.producer.partitionCount=2
# 隨機(jī)端口
server.port=0 # spring 自動隨機(jī)分配
eureka.instance.instance-id=${spring.application.name}:${random.int}
server.port=${random.int[10000,19999]} # 直接使用 random 指定
寫在最后
鄭重申明: 此篇系拜讀 D大 博客教程后筆記匯集而成, 感謝 D大 的分享.