date: 2017-12-18 13:28:54
title: 从 spring 中学习 -- D大教程学习笔记
description: swoft 一开始就有 spring cloud 这样的野心, 虽道阻且长, 吾辈亦当要上下求索
spring -> spring boot -> spring cloud
spring project: https://github.com/spring-projects
spring 教程: https://github.com/dyc87112
参与 swoft 开源项目 的开发工作, 开发组技术选型上比较倾向于 spring 的设计, 我在一块还比较薄弱, 所以开了这篇来补一补. 也许是给自己挖了一个巨大的坑, 不过我一向 乐天派 -- 万水千山只等闲, 且看今朝.
系列教程编写指南: 主题讲解 -> 代码示例 + 单元测试
spring boot
Spring-Boot基础教程
最为核心的两大要素: 依赖注入DI + 面向切面编程AOP
- 类 -> bean; 配置 -> 类定义/类属性
- 模板引擎: 静态资源位置 属性(数据)解析 默认参数配置
- apidoc - swagger
- 默认错误页面: 统一异常处理 + error page/json
- security 安全控制: AOP/拦截器 是否需要登录
- 数据库访问: JdbcTemplate
- Spring-data-jpa: Hibernate Entity->Repository 不同DB连接
- spring-data-redis: 存储对象 + 对象序列化接口
- spring-data-mongodb: Entity->Repository 配置->连接池
- mybatis-spring-boot-starter: Entity->Mapper
- Flyway: 数据库版本控制 migration
- spring-data-ldap: 轻量级目录访问协议 -> 用户管理体系
- cache: cache vs buffer; SpEL CacheManager 缓存生命周期的控制
- log: logger(commonLogging log4j logback) logFormat(时间/毫秒 日志级别/多环境/动态修改 进程id 分隔符 logger名 日志内容) logTarget(console/多彩 file-分类输出/package mongo)
- AOP: 日志切面 同步问题/记录函数执行时间/ThreadLocal 优先级问题/同一切入点多个切面
- mq: sender -> 队列/交换器/路由 -> receiver
- task: 同步 异步/异步回调 线程池 优雅关闭(平滑重启) Future->Runnable/Callable->任务取消/是否完成/获取结果->阻塞
- 邮件: 附件 静态资源 模板
- Actuator: 应用配置(配置刷新) 度量指标 操作控制
- cli StateMachine(state->event)
// 从注解中获取属性
@Value("${com.didispace.blog.name}")
private String name;
// web
@RestController() // 不需要 @ResponseBody
@Controller()
@ResponseBody()
@RequestMapping()
@PathVariable
@RequestParam
@ModelAttribute
// Swagger2
@ApiOperation(value="更新用户详细信息", notes="根据url的id来指定更新对象,并根据传过来的user信息来更新用户详细信息")
@ApiImplicitParams({
@ApiImplicitParam(name = "id", value = "用户ID", required = true, dataType = "Long"),
@ApiImplicitParam(name = "user", value = "用户详细实体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);
// 数据库事务
@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
// 等待任务全部执行完
while(true) {
if(task1.isDone() && task2.isDone() && task3.isDone()) {
// 三个任务都调用完成,退出循环等待
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());
// 优雅关闭
executor.setWaitForTasksToCompleteOnShutdown(true);
executor.setAwaitTerminationSeconds(60);
return executor;
}
@Test
# 参数引用
com.didispace.blog.desc=${com.didispace.blog.name}正在努力写《${com.didispace.blog.title}》
# 随机数
com.didispace.blog.value=${random.value}
# 命令行设置属性
java -jar xxx.jar --server.port=8888
# 多环境
spring.profiles.active=test
application-dev.properties # dev/test/prod
# 监控
/autoconfig // 配置信息
/beans
/configprops
/env
/mappings
/info
/metrics // 度量指标
/health
/dump
/trace
/shutdown // 操作控制
消息队列 MQ
message broker:
- 消息路由到 n 个目的地
- 消息转化为其他形式
- 消息聚集/分解 -> 目的地 -> 重新组合相应返回
- 调用web服务检索数据
- 响应事件/错误
- pub/sub 或 topic 的消息路由
AMQP:
- 消息方向
- 消息队列
- 消息路由(p2p pub/sub)
- 可靠性
- 安全性
状态机
- 状态/事件 枚举
- 状态机: 所有状态 + 初始状态
- 状态机: 状态迁移动作
- 状态机: 监听器
spring cloud
Spring-Cloud基础教程
高可用: 多节点/前置LB/注册为服务
- 共享资源的互斥访问 -> 分布式锁(全局锁) -> 基于redis 基于Zookeeper 基于Consul的KV存储实现分布式锁/信号量 -> 锁超时清理(超时时间)
- 限制并发线程/进程数量(并发控制) -> 信号量 -> Zuul默认情况使用信号量限制每个路由的并发数 Consul实现分布式信号量 -> PV操作
- 服务注册发现: Eureka Consul 服务注册中心(server)+服务提供方(client 服务清单->缓存)+服务消费者(consumer 客户端负载均衡)
- 服务消费者: loadBalancerClient Ribbon 轮询服务端列表达到负载均衡
- 服务消费工具 SC Feign: 声明式服务调用客户端 可插拔的注解 可插拔的编码器和解码器 整合Hystrix来实现服务的容错保护 引入Feign的扩展包实现文件上传
- 分布式配置中心 SC Config: 服务端/客户端 独立微服务应用 配置信息/加解密信息(dev->devops 敏感信息) 默认采用git来存储配置信息 配置git.username/git.password->http可访问 配置刷新->请求客户端actuator模块
- 服务保护机制 SC Hystrix: 服务降级fallback 服务熔断/断路器/快照时间窗/请求总数下限/错误百分比下限 线程隔离/为每一个依赖服务都分配一个线程池 信号量/延迟低/不能设置超时和异步访问 请求缓存 请求合并 服务监控 监控面板/turbine(消息聚合 http/mq)
- 服务网关 SC Zuul: 对外提供服务/服务路由/LB/权限控制/请求限流(过滤器ZuulFilter) 注册中心->服务清单->映射 使用Swagger汇总API接口文档 处理cookie/重定向/异常处理
- 消息驱动 SC Stream: RabbitMQ/Kafka pub/sub-消费组(避免消息重复消费)-消息分区(消息定向投递, 比如监控信息汇总) app->channel->binder(绑定器隐藏中间件细节, 暴露channel给app)->Middleware(mq中间件)
- 分布式服务跟踪 SC Sleuth: 全链路调用跟踪/快速发现错误根源/监控分析性能瓶颈 请求链路TraceID 基本工作单元SpanID->抽样Sampler logstash(ELK)日志收集
- 分布式服务跟踪整合 Zipkin: 收集器collector 存储组件Storage Api组件 WebUI http/mq方式收集
// mq
@StreamListener(Sink.INPUT)
@Input(Sink.INPUT)
SubscribableChannel input();
// 实现命名替换
@Component
spring.application.name=trace-1
server.port=9101
# 服务注册中心
eureka.client.serviceUrl.defaultZone=http://eureka.didispace.com/eureka/
# 服务消费者
ribbon.eager-load.enabled=true
ribbon.eager-load.clients=hello-service, user-service
# mq 生产
spring.cloud.stream.bindings.input.group=Service-A # 分组
spring.cloud.stream.bindings.input.destination=greetings
spring.cloud.stream.bindings.input.consumer.partitioned=true # 分区
spring.cloud.stream.instanceCount=2
spring.cloud.stream.instanceIndex=0
# mq 消费
spring.cloud.stream.bindings.output.destination=greetings # 分组
spring.cloud.stream.bindings.output.producer.partitionKeyExpression=payload # 分区
spring.cloud.stream.bindings.output.producer.partitionCount=2
# 随机端口
server.port=0 # spring 自动随机分配
eureka.instance.instance-id=${spring.application.name}:${random.int}
server.port=${random.int[10000,19999]} # 直接使用 random 指定
写在最后
郑重申明: 此篇系拜读 D大 博客教程后笔记汇集而成, 感谢 D大 的分享.