什么是SpringCloudAlibaba?
Spring Cloud Alibaba真实应用场景
Spring Cloud Alibaba与Spring Cloud
阅读本博客你能学到什么?
课程进阶会讲那些内容?
课程思路:
SpringCloud Alibaba的重要组件精讲,如图所示:
课上用到的软件
安装Maven:
安装JDK和MySQL
点击Create New Project -> Spring Initializr -> 配置JDK-> 选择Default-> 点击Next,如图所示:
根据自己实际配置好Group及Artifact,Type选Maven(若为Gradle项目也可以选Gradle),其他根据实际配置,如图所示:
选择需要的依赖,同时选择SpringBoot版本,其中如图所示的,除了2.1.5外,上方的都不是正式版,所以我们选择版本为2.1.5,如图所示:
我们可以自己部署一个Costom,结合官方文档和网上文章,当为内王环境时,自己就可以部署一个,如图所示:
通过启动启动类,直接就可以运行项目,我们不需要打war包再放到tomcat中了;
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
management.endpoint.health.show-details=always
management.endpoints.web.exposure.include=metrics,health
多个以逗号隔开
properties中的值若为 * ,在yml中则需要改为 “*”
yml>yaml>properties 执行顺序,如果有重复内容,则properties为最终的结果
java -jar xxx.jar --server.port=8081
微服务定义
微服务的特性:
微服务架构图:
微服务的优点:
微服务的缺点:
微服务的适用场景
微服务不适用的场景
xxx此处省略
npm install
npm --registry https://registry.npm.taobao.org install
npm run dev
npm run build
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>tk.mybatis</groupId>
<artifactId>mapper-spring-boot-starter</artifactId>
<version>2.1.5</version>
</dependency>
@MapperScan("xxx")
通用Mapper专用代码生成器生成的Model会在原有基础上增加@Table,@Id,@Column等注解,方便自动回数据库字段进行映射。运行MBG有多种方法,这里只是介绍两种比较常见的方法。并且有关的内容会针对这样的运行方式进行配置;
https://github.com/abel533/Mapper/blob/master/generator/src/test/java/tk/mybatis/mapper/generator/Generator.java
要使用这种方式,需要引入MBG的依赖,同时项目中应该已经有通用Mapper的依赖了。
将如图代码引入pom.xml中的对应位置:
maven-compiler-plgin在SpringBoot中提供了,我们可以不用加
在 resources/generator目录下创建generatorConfig.xml文件,如图所示:
可以去百度这个文件下的内容,将对应的占位符信息填充即可
如果有外部的config.properties引用,我们需要单独创建一个,将里面引入的比如username,password等信息在config.properties中补充,如图所示:
补充完,并且在generatorConifg.xml中配置好了所有的内容后,点击右侧Maven的插件: mybaits-generator 选择generate即可生成代码,如图所示:
它能生成model,也能生成通用maper的代码,如图所示:
作用: 简化代码编写,提升开发效率
项目主页:(https://www.prjectlombok.org/)[https://www.prjectlombok.org/]
IDEA中整合Lombok:
在IDEA中安装好了插件后,引入下方依赖(也可以自行百度最新版本)
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.8</version>
<scope>provided</scope>
</dependency>
使用@Getter,@Setter,@ToString,@EqualsAndHashCode可以自动生成getter,setter,tostring等方法,如图所示:
注解说明:
稳定功能和实验室功能,具体的可以在官网查看功能说明及示例;
lombok与代码生成器:
可以看到
userMapper
下有一个红色警告。虽然代码本身没有问题,能正常运行,但有个警告总归有点恶心。本文分析原因,并列出解决该警告的集中方案;
@Autowired
默认情况下要求依赖对象(也就是 userMapper
)必须存在。而IDEA认为注射个对象的实例/代理是个null,所以就友好的给个提示。@Resource
替换 @Autowired
@RequiredArgsConstructor()
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
不过这种方式也有缺点: 那就是如果你类之间的依赖比较复杂,特别是存在循环依赖(A引用B,B引用A,或者间接引用时),引用将会启动不起来…这其实是构造方法注入方式的缺点;
整合SpringCloud
整合SpringCloudAlibaba
第一种方式整合cloud以及Alibaba如图所示:
使用了如图所示内容后,我们在引入Cloud中的其他组件时,可以不指定版本,它会自动配置对应的版本。如果没有使用如图内容可能会导致依赖冲突,依赖不一致的情况;
sh startup.sh -m standalone
cmd startup.md
此处启动命令为单机模式,非集群模式
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
在SpringCloud中的依赖规则与SpringBoot类似。官方项目是以此作为结构:spring-cloud-starter-{spring cloud子项目的名称}-{模块名称},比如feign可以这样:spring-cloud-starter-openfeign,而sentinel可以这样:spring-cloud-starter-alibaba-sentinel,当没有模块的时候,就不用加模块了,比如feign就没有
spring:
cloud:
discovery:
server-addr: localhost:8848 #指定nacos server的地址
application:
name: 服务名称 # 比如 user-center,服务名称尽量用- ,不要用_
使用DiscoverClient的相关Api可以在代码中获取Nacos提供的微服务的一些信息,调用方法如图:
手写负载均衡器主要原理就是获取到此服务的所有url,然后以轮询、随机等方式进行调用指定的url
@RibonClient(name = "服务名称", configuration=RibbonConfiguration.class) public class XXXRibbonConfiguration{
}
@Configuration
public class RibbonConfiguration{
@Bean
public IRule ribbonRule(){
// 随机
return new RandomRule();
}
}
Ribbon的启动类不能被启动类扫描到,不然容易发生父子上下文重叠,出现各种bug问题;
xxx服务名称:
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule # 想要的规则的类的所在全路径
属性配置方式优先级更高。
代码配置和属性配置与之前上面的自定义配置一样;
application.yml
中进行配置: ribbon:
eager-load
enabled: true
clients: xxx服务名 # 多个服务,以 `,`号分割
此处为开启饥饿加载
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
@EnableFeignClients
注解@FeignClient(name="xxx服务名称")
public interface xxxFeignCLient{
/**
* 这是一个Feign的失恋了
*
* /
@GetMapping("xxx/xxx")
XXX findById(xxx xxx)
}
在配置类中,不能加@Configuration,如果加了必须在启动类能扫描的地方以外,否则会发生父子上下文异常,导致全局共享这个配置类
feign:
client:
config:
# 想要调用的微服务名称
user-center:
loggerLevel: full
@FeignClient("xxxx-xx")
public interface UserFeignClient{
@GetMapping("/get")
public User get0(@SpringQueryMap User user);
}
@FeignClient("xxxx-xx")
public interface UserFeignClient{
@GetMapping("/get")
public User get0(@RequestParam("id")Long id,@RequestParam("username")String str);
}
@FeignClient("xxxx-xx")
public interface UserFeignClient{
@GetMapping("/get")
public User get0(@RequestParam Map<String,Object>map);
}
在早期的Spring Cloud版本中,无需提供name属性,从Brixton版开始,@FeignClient必须提供name属性,否则应用将无法正常启动;
配置连接池(提升15%左右)
配置HttpClient:
<dependency>
<group>io.github.openfeign</groupId>
<artifactId>feign-httpclient</artifactId>
</dependency>
feign:
client:
config:
#全局配置
default:
loggerLevel: full
httpclient:
# 让Feign使用 apache httpclient做请求,而不是默认的urlHttp
enabled: true
# feign的最大连接数
max-connections:200
# feign单个路径的最大连接数
max-connections-per-route: 50
也可以使用OkHttp:
还有一个优化是降低日志级别,越低的日志级别,打印的日志越少,同时性能就越高
引入依赖:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
引入actuator
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
sentinel是懒加载
QPS 和线程数,是过滤条件,以哪种方式来进行过滤。单机阀值是达到多少量后进行流控;
流控模式有三种,分别是直接、关联和链路。直接就是最常见的一种模式,限流达到了,直接对这个接口生效。而流控效果就是生效的三种形式;快速失败也是最常用的一种,就是当达到阀值后,这个直接接口直接返回失败。而流控模式-关联 表示,当访问填入入口资源的路径的单机阀值触发了,此接口就会触发所选择的流控效果;而链路就表示只对此入口过来的数据对此接口触发限流规则;Warm up是热等待,它会在指定的时间后完成触发效果;排队等待是不返回失败,而是进行排队,一个处理完了,再去处理另外一个。
Warm Up
RT降级规则图示:
RT 默认最大4900ms,通过 -Dcsp.sentinel.statistic.max.rt=xxx 修改
注意点: 时间窗口 < 60秒可能会出问题 比如我们设置时间窗口为10秒,当触发降级内如果异常数依然触发降级,那么可能会再次降级
源码:
注意: 目前Sentinel没有半开状态,后期可能会推出半开;
源码地址:
这样就会统计这个流控,同时如果超过限流阀值,就会执行catch内的代码;
来源:
主要API:
后期我们可以用更简单的办法来实现,但都是基于此上图中的代码实现的;
使用此注解可以完成 8.13 的功能,手记地址: 手记地址
这里面的block可以处理限流或者降级。处理降级可以单独使用fallback关键字,然后也类似于block一样写一个方法即可;升级到sentinel 1.6 可以处理Throwable
相关源码:
配置好了重新刷新可能会没有数据展示,可以先去访问接口再回来刷新,因为它是懒加载模式;
网关gateway也可以实现类似集群流控的效果,且更加简单,性能更好;
不指定group会报异常,后期不知是否会处理这种;
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-spring-boot-starter</artifactId>
<version>2.0.3</version>
</dependency>
rocketmq:
name-server:127.0.0.1:9876
name-server的值根据每个人自身实际的ip及端口来填写,以我们安装的rocketmq地址来决定
@Data @NoArgsConstructor @AllArgsConstructor @Builder public class UserAddBOnusMsgDTO{
/** * 为谁加积分 */ private Integer userId; /** * 加多少积分 */ private Integer bonus; }
@Service
@RocketMQMessageListener(consumerGroup= "consumer-group",topic= "add-bonus")
public class AddBonusListener implements RocketMQListener<UserAddBonusMsgDTO>{
@Override
public void onMessage(UserAddBonusMsgDTO message){
System.out.println(message.getUserId())
System.out.println(message.getBonus())
}
}
注意:consumerGroup在生产者中是写到配置文件中的,在消费者中是在此处进行指定的。topic在生产者中是发送消息的时候添加,在此处是接收监听类的时候指定,这两个都必须带上;
简单来说RocketMQ实现分布式事务的原理是: 执行到应该发送消息的时候,它并未发送,而是处于“准备发送”阶段,当所有的代码都已执行完毕且无异常时,则进行完全发送,此刻消息消费者才能监听到消息;
到数据库中新增一张表,用来记录 RocketMQ的事务日志:
create table rocketmq_transaction_log(
id int auto_increment comment 'id' primary key,
transaction_Id varchar(45) not null comment '事务id',
log varchar(45) not null comment '日志')
消息生产者编写:发送半消息:
// 首先可以判断,当前面代码执行成功后再执行此代码,此处略
// 发送半消息
String transactionId=UUID.randomUUID().toString()
this.rocketMQTemplate.sendMessageInTransaction(
"tx-add-bonus-group","add-bonus",MessageBuilder.withPayload(
UserAddBonusMsgDTO.builder().userId(share.getUserId)
.bonus(50)
.build()
).setHeader(RocketMQHeaders.TRANSACTION_ID,transactionId)
.setHeader("share_id",id)
.build(),
auditDTO
)
此处 “tx-add-bonus-group”,“add-bonus” 组名及topic是由自己指定的,可根据实际改变。auditDTO、share_id是根据业务需要所传入的数据,auditDTO在消息监听类中可以直接强转使用,share_id的数据可以直接从请求头中获取;
消息消费者编写:
@RocketMQTransactionListener(txProducerGroup = "tx-add-bonus-group") @RequiredArgsConstructor(onConstructor = @_(@Autowired)) public class AddBonusTransactionListener implements RocketMQLocalTransactionListener{
@Override public RocketMQLocalTransactionState executeLocalTransaction(Message msg, Object arg) String transactionId(String)headers.get(RocketMQHeaders.TRANSACTION_ID); Integer shareId= Integer.valueOf((String)headers.get("share_id")) try{ this.shareService.auditByIdInDB(shareId,(ShareAuditDTO) arg) return RocketMQLocalTransactionState.COMMIT; }catch(Exception e){ return RocketMQLocalTransactionState.ROLLBACK; } } // 编写回查代码,当我们 @Override public RocketMQLocalTransactionState checkLocalTransaction(Message msg){ return null; } }
当我们执行成功,则执行RocketMQLocalTransactionState.COMMIT,失败则ROLLBACK。但是有这样一种情况,比如我们已经执行完逻辑代码,正准备COMMIT提交,此时突然停电了,导致数据已经存入,但是却没有提交成功。所以我们需要一个回查方法,checkLocalTransaction()是一个回查方法,它会去里面进行判断是否执行成功。结合我们已经建立的RocketMQ事务表,我们可以进行回查操作,代码看下方:
// auditByInDB具体方法内容如图所示:
新建一个存入方法,我们之前的存入方法,没有将事务数据加入日志表,我们可以这样改造: 当数据存入的时候,将数据存入日志表;回查方法就进行回查,如果没有存入则表示执行失败:
@Autowired
private RocketmqTransactionLogMapepr rocketmqTransactionLogMapepr;
@Transactional(rollbackFor= Exception.class)
public void auditByIdWithRocketMqLog(Integer id, ShareAuditDTO auditDTO, String transactionId){
this.auditByIdInDB(id,auditDTO);
this.rocketmqTransactionLogMapper.insertSelective(
RocketmqTransactionLog.builder().transactionId(transactionId)
.log(“审核分享”)
.build()
);
}
消息消费者重写:
@Autowired private ShareService shareService; @Autowired priavte RocketmqTransactionLogMapper rocketmqTransactionLogMapper; @RocketMQTransactionListener(txProducerGroup = "tx-add-bonus-group") @RequiredArgsConstructor(onConstructor = @_(@Autowired)) public class AddBonusTransactionListener implements RocketMQLocalTransactionListener{
@Override public RocketMQLocalTransactionState executeLocalTransaction(Message msg, Object arg) String transactionId(String)headers.get(RocketMQHeaders.TRANSACTION_ID); Integer shareId= Integer.valueOf((String)headers.get("share_id")) try{ this.shareService.auditByIdWIthRocketMqLog(shareId,(ShareAuditDTO) arg,transactionId) return RocketMQLocalTransactionState.COMMIT; }catch(Exception e){ return RocketMQLocalTransactionState.ROLLBACK; } } // 编写回查代码,当我们 @Override public RocketMQLocalTransactionState checkLocalTransaction(Message msg){ MessageHeaders headers= msg.getHeaders(); String transactionId= (String) headers.get(RocketMQHeaders.TRANSACTION_ID); // 查询是否存了事务数据 this.rocketmqTransactionLogMapper.selectOne(RocketmqTransactionLog.builder().transactionId(transactionId).build()); // 判断是否提交 if(transactionLog != null){ return RocketMQLocalTransactionState.COMMIT; } return RocketMQLocalTransactionState.ROLLBACK; } }
使用header和arg可以传参
当消息生产者使用Kafka发送消息,那只能用Kafka来接收消息。当使用SpringCloudStream来处理消息的话,我们接收Kafka的消息,可以使用其他的消息中间件来进行接收。SpringCloudStream对消息进行了一层封装,所以我们不需要去关心生产者用的是什么消息中间件。
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-stream-rocketmq</artifactId>
</dependency>
spring:
cloud:
stream:
rocketmq:
binder:
name-server: 127.0.0.1:9876
bindings:
output:
# 用来指定topic
destination: stream-test-topic
@GetMapping("test-stream")
public String testStream(){
this.source.output()
.send(
MessageBuilder
.withPayload("消息体")
.build()
);
return "success";
}
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-stream-rocketmq</artifactId>
</dependency>
spring:
cloud:
stream:
rocketmq:
binder:
name-server: 127.0.0.1:9876
bindings:
input:
destination: stream-test-topic
group: binder-group # 这里的group 一定要设置; 如果使用的不是rocketmq的话,这里可以不用设置,可以留空
@Service @Slf4j public class TestStreamConsumer{ @StreamListener(Sink.INPUT) public void receive(String messageBody){ log.info("通过stream收到了消息: messageBody = {}");
} }
public interface MySource{ String MY_OUTPUT= "my-output";
@Output(MY_OUTPUT) MessageChannel output(); }
使用自定义的接口我们可以进行消息的收发;
@StreamListener("errorChannel")
public void error(Message<?> message){
ErrorMessage errorMessage= (ErrorMessage) message;
log.warn("发生异常,errorMessage = {}",errorMessage);
}
gateway是基于Netty,所以启动速度非常快。从上面就已经可以进行服务转发了,因为gateway:discovery:locator:enabled:true 可以自动让服务转发到对应的路径去;
这些过滤器工厂的核心Api比较简单,从名称就可以看出其含义;
sentinel要在1.6 版本以后才支持gateway
看好Keyclock,但是它不支持CLoud,它是Servlet模型的,无法与Gateway配合使用;
它安全性和性能都不占优势,但它的优点是能够与老项目服务相兼容
安全性低,性能高
建议使用SpringAop 来实现,这样解耦且灵活
使用Nacos就可以作为一个配置服务器,实现上面两个功能;
添加依赖:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
建立bootstrap.yml:
spirngconfig与Nacos的配置是分开来的。config的配置建议放在bootstrap.yml中,否则可能不生效,应用无法读取配置;
配置好内容后点击发布即可。
重启应用,调用接口,发现参数能获取到,完成!
配置共享:
这个自动方式就是里面的内容是所有环境的公共配置数据,profiles.active指向的环境可以放置其存在变化的配置;
服务发现组件是放在文件夹内。配置数据是放在嵌入式数据库中(生产环境建议更换为mysql);
里面每一行数据,可以理解为一个span
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-sleuth</artifactId>
</dependency>
curl -sSL https://zipkin.io/quickstart.sh | bash -s
https://search.maven.org/remote_content?g=io.zipkin.java&a=zipkin-server&v=1
下载下来的文件名为: zipkin-server-2.12.9-exec.jar
https://pan.baidu.com/s/1HXjzNDpzin6fXGrZPyQeWQ
密码:aon2
java -jar zipkin-server-2.12.9-exec.jar
compile group: 'org.springframework.cloud', name: 'spring-cloud-sleuth-zipkin', version: '2.2.3.RELEASE'
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-sleuth-zipkin</artifactId>
<version>2.2.3.RELEASE</version>
</dependency>
抽样率不是越高越好,也不是越低越好。抽样率高的话,分析的更加准确,但是它的性能消耗会更严重;
localhost:9411
的服务…这个服务根本不存在,所以会一直报异常;discoveryClientEnabled: false
,如图所示:这里的小驼峰命名是因为有一个小bug,后面会修复就以 - 隔开
持久化方式:
下载ElasticSearch:( 建议使用5,6,7版本)
./elasticsearch
或后台执行 ./elasticsearch -d
STORAGE_TYPE
和ES_HOSTS
,然后执行zipkin server服务:Zipkin其他环境变量:https://github.com/openzipkin/zipkin/tree/master/zipkin-server#environment-variables
spark job
才能分析出依赖关系图,使用方式如下:它是zipkin的子项目,第一步下载,第二步启动
可以编写脚本每日执行;
Alibaba Java Coding Guidelines
tail -f ../../logs/sonar.log
内嵌数据库不方便伸缩,所以不适用于生产环境,所以建议更换为MySql之类的数据库
SpringBoot Admin:
搭建SpringBoot admin 步骤:
@EnableAdminServer
注解,如图所示:被监控的服务的步骤:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
jvisualvm
即可,如图所示:jvisualvm与jconsole类似,但是它的功能更强大一点,但是它们都是客户端形式,希望能出一款强大的web浏览形式的监控工具;
Synchronize 'xxxx'
,生成出gc.log,如图所示:Reveal in Finder
,将文件导出:gceasy.io
中,点击选择文件打开,然后会生成统计图表对日志监控的工具可以不做强制要求,只要合适就行,不一定得必须要ELK
它未来是SpringCloudAlibaba的一个子项目