为了让SpringBoot帮我们完成各种自动配置,我们必须引入SpringBoot提供的自动配置依赖,我们称为启动器
java配置主要靠java类和一些注解,比较常用的注解有:
@Configuration:声明一个类作为配置类,代替xml文件
@Bean:声明在方法上,将方法的返回值加入Bean容器,代替标签
@value:属性注入
@PropertySource:指定外部属性文件,
@SpringBootApplication注解有3个:
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan
一般启动类会放在一个比较前的包目录中。
SpringBoot为我们提供了默认配置,而默认配置生效的条件一般有两个:
你引入了相关依赖
你自己没有配置Bean
1)启动器
所以,我们如果不想配置,只需要引入依赖即可,而依赖版本我们也不用操心,因为只要引入了SpringBoot提供的stater(启动器),就会自动管理依赖及版本了。
因此,玩SpringBoot的第一件事情,就是找启动器,SpringBoot提供了大量的默认启动器,参考课前资料中提供的《SpringBoot启动器.txt》
2)全局配置
另外,SpringBoot的默认配置,都会读取默认属性,而这些属性可以通过自定义application.properties文件来进行覆盖。这样虽然使用的还是默认配置,但是配置中的值改成了我们自定义的。
因此,玩SpringBoot的第二件事情,就是通过application.properties来覆盖默认属性值,形成自定义配置。我们需要知道SpringBoot的默认属性key,非常多
默认的静态资源路径为:
classpath:/META-INF/resources/
classpath:/resources/
classpath:/static/
classpath:/public
你会发现日志中什么都没有,因为我们记录的log级别是debug,默认是显示info以上,我们需要进行配置。
SpringBoot通过logging.level.=debug来配置日志级别,填写包名
SpringBoot整合SpringMVC
SpringBoot整合jdbc和事务
SpringBoot整合连接池
SpringBoot整合mybatis
系统架构的演变
集中式架构
垂直拆分
分布式服务
服务治理(SOA)
微服务
SOA :面向服务的架构 Service Oriented Architecture
前面说的SOA,英文翻译过来是面向服务。微服务,似乎也是服务,都是对系统进行拆分。因此两者非常容易混淆,但其实缺有一些差别
微服务的特点:
单一职责:微服务中每一个服务都对应唯一的业务能力,做到单一职责
微:微服务的服务拆分粒度很小,例如一个用户管理就可以作为一个服务。每个服务虽小,但“五脏俱全”。
面向服务:面向服务是说每个服务都要对外暴露Rest风格服务接口API。并不关心服务的技术实现,做到与平台和语言无关,也不限定用什么技术实现,只要提供Rest的接口即可。
自治:自治是说服务间互相独立,互不干扰
远程调用方式
无论是微服务还是SOA,都面临着服务间的远程调用。那么服务间的远程调用方式有哪些呢?
常见的远程调用方式有以下几种:
RPC:Remote Produce Call远程过程调用,类似的还有RMI。自定义数据格式,基于原生TCP通信,速度快,效率高。早期的webservice,现在热门的dubbo,都是RPC的典型
Http:http其实是一种网络传输协议,基于TCP,规定了数据传输的格式。现在客户端浏览器与服务端通信基本都是采用Http协议。也可以用来进行远程服务调用。缺点是消息封装臃肿。
现在热门的Rest风格,就可以通过http协议来实现
RPC,即 Remote Procedure Call(远程过程调用),是一个计算机通信协议。 该协议允许运行于一台计算机的程序调用另一台计算机的子程序,而程序员无需额外地为这个交互作用编程。说得通俗一点就是:A计算机提供一个服务,B计算机可以像调用本地服务那样调用A计算机的服务
RPC调用流程图
自己动手实现RPC
Http客户端工具
* HttpClient
* OKHttp
* URLConnection
JSON转换工具
HttpClient请求数据后是json字符串,需要我们自己把Json字符串反序列化为对象,我们会使用JacksonJson工具来实现。
JacksonJson是SpringMVC内置的json处理工具,其中有一个ObjectMapper类,可以方便的实现对json的处理
json转集合
@Test
public void testJson3() throws IOException {
User user = new User();
user.setId(8L);
user.setAge(21);
user.setName("柳岩");
user.setUserName("liuyan");
// 序列化,得到对象集合的json字符串
String json = objectMapper.writeValueAsString(Arrays.asList(user, user));
// 反序列化,接收两个参数:json数据,反序列化的目标类字节码
List<User> users = objectMapper.readValue(json, objectMapper.getTypeFactory().constructCollectionType(List.class, User.class));
for (User u : users) {
System.out.println("u = " + u);
}
}
json转任意复杂类型
public void testJson4() throws IOException {
User user = new User();
user.setId(8L);
user.setAge(21);
user.setName("柳岩");
user.setUserName("liuyan");
// 序列化,得到对象集合的json字符串
String json = objectMapper.writeValueAsString(Arrays.asList(user, user));
List<User> list = objectMapper.readValue(json, new TypeReference<List<User>>() {});
for (User user1 : list) {
System.out.println("user1 = " + user1);
}
}
当对象泛型关系复杂时,类型工厂也不好使了。这个时候Jackson提供了TypeReference来接收类型泛型,然后底层通过反射来获取泛型上的具体类型。实现数据转换。
Spring的RestTemplate
Spring提供了一个RestTemplate模板工具类,对基于Http的客户端进行了封装,并且实现了对象与json的序列化和反序列化,非常方便。RestTemplate并没有限定Http的客户端类型,而是进行了抽象,目前常用的3种都有支持:
* HttpClient
* OkHttp
* JDK原生的URLConnection(默认的)
通过RestTemplate的getForObject()方法,传递url地址及实体类的字节码,RestTemplate会自动发起请求,接收响应,并且帮我们对响应结果进行反序列化。
分布式事务
跨数据源或者跨服务的事务,这就是分布式事务。
在分布式系统中 就不得不面对CAP理论
CAP定理是由加州大学伯克利分校Eric Brewer教授提出来的,他指出WEB服务无法同时满足一下3个属性:
一致性(Consistency) : 客户端知道一系列的操作都会同时发生(生效)
可用性(Availability) : 每个操作都必须以可预期的响应结束
分区容错性(Partition tolerance) : 即使出现单个组件无法可用,操作依然可以完成
具体地讲在分布式系统中,在任何数据库设计中,一个Web应用至多只能同时支持上面的两个属性。显然,任何横向扩展策略都要依赖于数据分区。因此,设计人员必须在一致性与可用性之间做出选择。
AP –> 牺牲C (最终一致性)
而面对高并发的互联网行业,高可用显然会比一致性要重要的多,但是一致性也是不可抛弃的,如何解决这一矛盾?这个时候,大神们有总结出了BASE理论:
* Basically Available(基本可用)
* Soft state(软状态)
* Eventually consistent(最终一致性)
BASE理论是对CAP中的一致性和可用性进行一个权衡的结果,理论的核心思想就是:我们无法做到强一致,但每个应用都可以根据自身的业务特点,采用适当的方式来使系统达到最终一致性(Eventual consistency)。
柔性事务和刚性事务
刚性事务:严格遵循ACID原则的事务, 例如单机环境下的数据库事务。
柔性事务:指遵循BASE理论(基本可用,最终一致)的事务,通常用在分布式事务中。
因为分布式事务的特点,只能采用柔性事务,常用的柔性事务解决方案有:
* 两阶段提交(Two Phase Commit, 2PC)
* 有成熟的解决框架,实现简单。
* 资源锁定周期长,执行效率低,不适合高并发场景
* 故障恢复困难
* TCC补偿型事务(Try-Confirm-Cancle)
* 实现很复杂,开发成本高
* 资源锁定粒度小,执行效率高
* 强隔离性,严格的数据一致性
* 事务执行周期短
* 异步确保,基于可靠MQ服务
* 实现较为复杂,成本略高
* 对MQ的可靠性要求高
* 事务执行周期长
TCC是基于业务层面的事务定义。锁粒度完全由业务自己控制。它本质是一种补偿的思路。它把事务运行过程分成 Try、Confirm / Cancel 两个阶段。在每个阶段的逻辑由业务代码控制。这样就事务的锁粒度可以完全自由控制。业务可以在牺牲隔离性的情况下,获取更高的性能。
* Try 阶段
* Try :尝试执行业务
* 完成所有业务检查( 一致性 )
* 预留必须业务资源( 准隔离性 )
* Confirm / Cancel 阶段:
* Confirm :确认执行业务
* 真正执行业务
* 不做任务业务检查
* Confirm 操作满足幂等性
* Cancel :取消执行业务
* 释放 Try 阶段预留的业务资源
* Cancel 操作满足幂等性
* Confirm 与 Cancel 互斥
TCC实例
在电商网站中,一个经典的案例是这样的:用户下单,基于订单系统实现,操作订单表;同时下单需要对商品进行减库存操作,在库存系统完成;同时还要对用户进行积分奖励,在用户中心完成。这样就出现了分布式事务。
我们用这个业务场景来解释TCC的过程:
* try:尝试执行业务
* 完成业务检查、预留必须业务资源
* 在本例中:判断库存是否充足,如果充足,锁定库存数据。
* confirm/cancel:
* Confirm :
* 确认执行业务,利用try阶段预留的资源进行操作,如果失败还要重试,因此要保证接口的幂等性
* 在本例中,我们在库存系统减库存,同时在用户中心给用户增加积分。
* Cancel :
* 如果confirm阶段出现异常、超时。则调用cancel取消执行业务,进行事务补偿(例如逆向操作)。释放try阶段锁定的资源。如果在事务补偿过程中出现异常,也必须进行重试。如果超过重试次数后,依然无法成功,则记录日志,以便后续人工介入进行事务补偿操作。
* 在本例中,我们尝试恢复库存。同时取消用户积分。
TCC的优缺点
优点:
TCC的try、confirm等操作全部有用户定义,因此锁定粒度由用户自由控制。各个资源独立锁定,分别提交、释放,无需等待对方。失败后是执行cancel中的补偿型操作即可。
事务执行效率高,时间短
能够保证严格的数据一致性。
缺点:
正是因为需要用户编写所有的try、confirm、cancel操作,另外还有重试、幂等的要求,日志的记录等,实现复杂度较高,开发成本增加。
目前有一些开源的TCC框架:
TCC-transaction: https://github.com/changmingxie/tcc-transaction
spring-cloud-rest-tcc: https://github.com/prontera/spring-cloud-rest-tcc
happylifeplat-transaction: https://github.com/yu199195/happylifeplat-transaction
TCC使用场景
对一致性要求较高,事务时效性比较敏感的业务。
day16-注册及阿里大于资料中的关于分布式事务的讲解:
付款减库存还是下单减库存?
一般采用下单减库存,用户体验好。
下单减库存,就必须设置付款有效时间。
减库存是否有线程安全问题?如何解决?
只有满足 共享变量 多线程并发 多步骤 就可能会发生线程安全问题
1)查询库存余量
2)判断库存是否充足
3)减库存 下单成功
并发访问情况下,因为是多线程操作,而且查询和修改是分开的,因此就会出现超卖现象。
为了避免超卖,解决方案:
a:加锁
1)悲观锁:认为线程安全问题一定会发生。在每次查询都锁表。select….for update
2)乐观锁:认为线程安全问题不会发生。查询时不锁表,在修改数据时做校验,校验数据是否有被修改过
update tb_item as num=num-1 where id=1 and num=10;
刚才的乐观锁频率过高,导致数据库压力过大,很有可能就崩溃了。
b:变同步写为异步写
思路:不再直接操作数据库,而是把库存量读取到内存中,在内存中完成减库存操作。再把生成订单和减库存的任务通过MQ交给数据库操作。
lpush 放入10条数据进入队列
lpop
如何在内存中判断库存是否有剩余?
用redis就可以实现。
方案1:用简单的key和value结构。利用redis事务监控该字段(利用redis的乐观锁操作 watch)
set num 10
watch
get num
if(num>0){
set num =9
}
方案2:用list结构(队列)。初始化向队列中填等于库存量的数据。每次请求到达调用lpop取,根据返回结果是否为null 判断是否失败。
解决数据库的并发写问题。
解决如何避免用户不断刷新页面,nginx压力过大问题
静态化技术:把动态jsp页面转化为静态的HTML。然后所有的请求不再到达tomcat,而是直接由nginx处理