(1)Hystrix(https://github.com/Netflix/Hystrix)是由Netflix开源的一个延迟和容错库,用于隔离访问远程系统、服务或者第三方库,防止级联失败,从而提升系统的可用性、容错性与局部应用的弹性,是一个实现了超市机制和断路器模式的工具类库。
1)创建基于Eureka和Ribbon的服务端和两个客户端生产者,消费者。
2)在消费者中添加依赖。
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix</artifactId>
</dependency>
3)在启动类ClientApplication上添加@EnableHystrix或@EnableCircuitBreaker。
4)修改HystrixController,在其中添加getUser方法,并添加熔断回调方法注解及回调方法。
@HystrixCommand(fallbackMethod = "getDefaultUser")
@RequestMapping("/getUser")
public String getUser() {
return restTemplate.getForObject("http://client/getUser", String.class);
}
private String getDefaultUser() {
System.out.println("熔断,默认回调函数");
return "{\"username\":\"admin\",\"age\":\"-1\"}";
}
(2)基于Feign使用Hystrix
通常情况下的Hystrix是通过注解@HystrixCommand的fallbackMethod属性实现回调的,而在Feign中,由于Feign是用接口实现的声明式Rest,所以Hystrix的通用方法在这里就不适用于Feign了,实际上在Feign与SpringCloud的依赖库中已经默认的将Hystrix加入其中了。
1)导入依赖。
pom.xml
:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-feign</artifactId>
</dependency>
2)application.properties
属性配置文件:
server.port=8762
spring.application.name=client-8762
#默认feign的hystrix为关闭状态
feign.hystrix.enabled=true
eureka.client.service-url.defaultZone=http://localhost:8761/eureka/
3)启动类添加@EnableFeignClients,控制层通过注入feign的接口去完成声明式调用:
a) feign的接口:
package com.cn.feign;
import org.springframework.cloud.netflix.feign.FeignClient;
import org.springframework.web.bind.annotation.RequestMapping;
//接口类上加入的注解中添加属性fallback,指定回调类
@FeignClient(name = "CLIENT-87",fallback = FeignClientFallback.class)
public interface UserFeign {
@RequestMapping("/getUser")
public String getUser();
}
b) 创建回调类:
package com.cn.feign;
import org.springframework.stereotype.Component;
@Component
class FeignClientFallback implements UserFeign {
@Override
public String getUser() {
System.out.println("熔断,默认回调函数");
return "{\"username\":\"admin\",\"age\":\"-1\"}";
}
}
(1)自动生成代码步骤
<!-- https://mvnrepository.com/artifact/org.mybatis.generator/mybatis-generator-core -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>2.3</version>
</dependency>
2)创建代理生成器主类。
package com.atguigu.eduservice;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.generator.AutoGenerator;
import com.baomidou.mybatisplus.generator.config.DataSourceConfig;
import com.baomidou.mybatisplus.generator.config.GlobalConfig;
import com.baomidou.mybatisplus.generator.config.PackageConfig;
import com.baomidou.mybatisplus.generator.config.StrategyConfig;
import com.baomidou.mybatisplus.generator.config.rules.DateType;
import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;
import org.junit.Test;
/**
* @author
* @since 2018/12/13
*/
public class CodeGenerator {
@Test
public void run() {
// 1、创建代码生成器
AutoGenerator mpg = new AutoGenerator();
// 2、全局配置
GlobalConfig gc = new GlobalConfig();
String projectPath = System.getProperty("user.dir");
gc.setOutputDir(projectPath + "/src/main/java");
gc.setAuthor("testjava");
gc.setOpen(false); //生成后是否打开资源管理器
gc.setFileOverride(false); //重新生成时文件是否覆盖
gc.setServiceName("%sService"); //去掉Service接口的首字母I
gc.setIdType(IdType.ID_WORKER_STR); //主键策略
gc.setDateType(DateType.ONLY_DATE);//定义生成的实体类中日期类型
gc.setSwagger2(true);//开启Swagger2模式
mpg.setGlobalConfig(gc);
// 3、数据源配置
DataSourceConfig dsc = new DataSourceConfig();
dsc.setUrl("jdbc:mysql://localhost:3306/guli_edu?serverTimezone=GMT%2B8");
dsc.setDriverName("com.mysql.jdbc.Driver");
dsc.setUsername("root");
dsc.setPassword("root");
dsc.setDbType(DbType.MYSQL);
mpg.setDataSource(dsc);
// 4、包配置
PackageConfig pc = new PackageConfig();
//com.atguigu.eduservice
pc.setModuleName("eduservice"); //模块名
pc.setParent("com.atguigu");
pc.setController("controller");
pc.setEntity("entity");
pc.setService("service");
pc.setMapper("mapper");
mpg.setPackageInfo(pc);
// 5、策略配置
StrategyConfig strategy = new StrategyConfig();
strategy.setInclude("edu_chapter","edu_video");
strategy.setNaming(NamingStrategy.underline_to_camel);//数据库表映射到实体的命名策略
strategy.setTablePrefix(pc.getModuleName() + "_"); //生成实体时去掉表前缀
strategy.setColumnNaming(NamingStrategy.underline_to_camel);//数据库表字段映射到实体的命名策略
strategy.setEntityLombokModel(true); // lombok 模型 @Accessors(chain = true) setter链式操作
strategy.setRestControllerStyle(true); //restful api风格控制器
strategy.setControllerMappingHyphenStyle(true); //url中驼峰转连字符
mpg.setStrategy(strategy);
// 6、执行
mpg.execute();
}
}
(2)添加自定义 sql语句步骤
1)xml 映射文件中添加自定义sql语句。
resultType返回值类型,map是别名,代表的是java.util.Map;基本数据类型考虑到重复的问题,会在其前面加上 ‘_’,比如 byte 对应的别名是 _byte;引用数据类型是将大写字母转小写字母,比如 HashMap 对应的别名是 hashmap;也可以返回 JavaBean 类型。
resultType和resultMap只能使用一个。parameterType是String时,参数名必须是_parameter
${}和#{}的区别:#{}将给参数自动加上单引号’’,而${}不会
2)Mapper接口中添加方法。
查询结果是多条数据的,也可以封装成Map:{表中主键, JavaBean},而不用List。同时resultType 返回值类型,不再是map,而是Map 的 value 对应的JavaBean类型。
3)业务层调用接口并实现,表示层调用业务层方法。
(3)如何防止再次自动生成代码覆盖掉之前已经添加的代码
目前的解决方式:
目前此种方法的缺陷:
1)需要手动操作,拷来拷去的繁琐;
2)手动操作容易误操作和遗漏;
3)项目新成员无意识或意识欠缺,改表后重新生成时容易遗忘把手写部分的sql拷回去,造成线上安全问题;
分析:
mybatis generator自动生成时可选是“追加”还是“覆盖”,不论追加还是覆盖,都不能达到我们想要的同一个文件既能自动修改原来的代码,又能不变我们手写追加的代码。
解决方法:
把我们手写的sql放到一个扩展的mapper和xml中,继承原生的mapper和xml,这样就可以每次重新生成时就只覆盖原生的表而不影响到我们自写的代码。
(1)springcloud基于 HTTP 的 REST 方式。
(2)可以将http改为https(http + ssl = https)。
1)HTTP存在的几点不足:
通信使用明文,内容可能会被窃听;不验证通信方的身份,因此有可能遭遇伪装;无法证明报文的完整性,有可能已遭篡改。
2)《网络安全法》的相关规定。
3)越来越多场景的强制性要求。
IOS、谷歌等要求的 APP 分发下载必须使用 https 安全连接;
微信小程序、支付宝小程序强制使用 HTTPS;
银行支付相关业务必须是HTTPS。
4)利于网站搜索引擎优化。
搜索引擎如谷歌,百度站在确保用户信息安全的角度,都在大力倡导网站部署SSL证书实现https加密访问。在搜索、展现、排序方面也给予部署了SSL证书网站优待。
判断的时候先根据hashcode进行的判断,相同的情况下再根据equals()方法进行判断。如果只重写了equals方法,而不重写hashcode的方法,会造成hashcode的值不同,而equals()方法判断出来的结果为true。
在Java中的一些容器中,不允许有两个完全相同的对象,插入的时候,如果判断相同则会进行覆盖。这时候如果只重写了equals()的方法,而不重写hashcode的方法,Object中hashcode是根据对象的存储地址转换而形成的一个哈希值。这时候就有可能因为没有重写hashcode方法,造成相同的对象散列到不同的位置而造成对象的不能覆盖的问题。
第一个if减少性能开销,第二个if避免生成多个对象实例。
现有三个线程A,B,C,假设线程A和线程B同时调用getSingleton()时,判断第一层if判断都为空,这时线程A先拿到锁,线程B在代码块外层等待。线程A进行第二层if判断,条件成立后new了一个新对象,创建完成,释放锁,线程B拿到锁,进行第二层if判断,singleton不为空,直接返回singleton释放锁,避免生成多个对象实例。线程线C调用getSingleton时第一层判断不成立,直接拿到singleton对象返回,避免进入锁,减少性能开销。
使用volatile是因为singleton = new Singleton();这行代码并不是一个原子指令,可能会在JVM中进行指令重排,volatile可以禁止指令重排序。
(1)private:只能在本类中才可以访问。
(2)default:同包下才可以访问。
(3)protected:同包下和不同包下的子类才可以访问。
(4)public:任何地方都可以访问。
ArrayList和LinkedList都是实现了List接口的容器类,用于存储一系列的对象引用。他们都可以对元素的增删改查进行操作。
ArrayList,在集合的末尾删除或添加元素所用的时间是一致的,但是在列表中间的部分添加或删除时所用时间就会大大增加。但是它在根据索引查找元素的时候速度很快。
LinkedList则相反,在插入、删除集合中任何位置的元素所花费的时间都是一样的,但是它根据索引查询一个元素的时候却比较慢。
ArrayList和LinkedList的大致区别:
(1)ArrayList是实现了基于动态数组的数据结构,LinkedList是基于链表结构。
(2)对于随机访问的get和set方法,ArrayList要优于LinkedList,因为LinkedList要移动指针。
(3)对于新增和删除操作add和remove,LinkedList比较占优势,因为ArrayList要移动数据。
性能优缺点:
(1)对ArrayList和LinkedList而言,在列表末尾增加一个元素所花的开销都是固定的。对 ArrayList而言,主要是在内部数组中增加一项,指向所添加的元素,偶尔可能会导致对数组重新进行分配;而对LinkedList而言,这个开销是统一的,分配一个内部Entry对象。
(2)在ArrayList集合中添加或者删除一个元素时,当前的列表所所有的元素都会被移动。而LinkedList集合中添加或者删除一个元素的开销是固定的。
(3)LinkedList集合不支持 高效的随机随机访问(RandomAccess),因为可能产生二次项的行为。
(4)ArrayList的空间浪费主要体现在在list列表的结尾预留一定的容量空间,而LinkedList的空间花费则体现在它的每一个元素都需要消耗相当的空间。
所以在进行对元素的增删查操作的时候,进行查操作时用ArrayList,进行增删操作的时候最好用LinkedList。
sychronized 加锁的时候,会调用 objectMonitor 的 enter 方法,解锁的时候会调用exit方法。事实上,只有在JDK1.6之前,synchronized 的实现才会直接调用 ObjectMonitor 的 enter 和 exit,这种锁被称之为重量级锁。
JDK1.6中出现对锁进行了很多的优化,进而出现轻量级锁,偏向锁,锁消除,适应性自旋锁,锁粗化(自旋锁在1.4就有 只不过默认的是关闭的,jdk1.6是默认开启的),这些操作都是为了在线程之间更高效的共享数据 ,解决竞争问题。
lock的存储结构:一个int类型状态值(用于锁的状态变更),一个双向链表(用于存储等待中的线程)。
lock获取锁的过程:本质上是通过CAS来获取状态值修改,如果当场没获取到,会将该线程放在线程等待链表中。
lock释放锁的过程:修改状态值,调整等待链表。
在整个实现过程中,lock大量使用CAS+自旋。因此根据CAS特性,lock建议使用在低锁冲突的情况下。目前java1.6以后,官方对synchronized做了大量的锁优化(偏向锁、自旋、轻量级锁)。因此在非必要的情况下,建议使用synchronized做同步操作。
使用EXPLAIN关键字可以模拟优化器执行SQL查询语句,从而知道MySQL是 如何处理你的SQL语句的。分析你的查询语句或是表结构的性能瓶颈。
使用方式:Explain+SQL语句
执行计划包含的信息:
±—±------------±------±-----±--------------±-----±--------±-----±-----±------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
±—±------------±------±-----±--------------±-----±--------±-----±-----±------+
(1)Id
SELECT查询的序列号,包含一组数字,表示查询中执行SELECT语句或操作表的顺序
包含三种情况:
1)id相同,执行顺序由上至下。
2)id不同,如果是子查询,id序号会递增,id值越大优先级越高,越先被执行。
3)id既有相同的,又有不同的。id如果相同认为是一组,执行顺序由上至下; 在所有组中,id值越大优先级越高,越先执行。
(2)Select_type
1)SIMPLE:简单SELECT查询,查询中不包含子查询或者UNION。
2)PRIMARY:查询中包含任何复杂的子部分,最外层的查询。
3)SUBQUERY:SELECT或WHERE中包含的子查询部分。
4)DERIVED:在FROM中包含的子查询被标记为DERIVER(衍生), MySQL会递归执行这些子查询,把结果放到临时表中。
5) UNION:若第二个SELECT出现UNION,则被标记为UNION, 若UNION包含在FROM子句的子查询中,外层子查询将被标记为DERIVED。
6)UNION RESULT:从UNION表获取结果的SELECT。
(3)Table
数据是关于哪张表的。
(4)Type
Type显示的是访问类型,是较为重要的一个指标,结果值从最好到最坏依次是:
system>const>eq_ref>ref>fulltext>ref_or_null>index_merge>unique_subquery>index_subquery>range>index>ALL
一般来说,得保证查询至少达到range级别,最好能达到ref。
1)system:表只有一行记录(等于系统表),这是const类型的特例,平时不会出现。
2)const:如果通过索引依次就找到了,const用于比较主键索引或者unique索引。 因为只能匹配一行数据,所以很快。如果将主键置于where列表中,MySQL就能将该查询转换为一个常量。
3)eq_ref:唯一性索引扫描,对于每个索引键,表中只有一条记录与之匹配。常见于主键或唯一索引扫描。
4)ref:非唯一性索引扫描,返回匹配某个单独值的所有行。本质上也是一种索引访问,它返回所有匹配 某个单独值的行,然而它可能会找到多个符合条件的行,所以它应该属于查找和扫描的混合体。
5)range:只检索给定范围的行,使用一个索引来选择行。key列显示使用了哪个索引,一般就是在你的where语句中出现between、<、>、in等的查询,这种范围扫描索引比全表扫描要好,因为只需要开始于缩印的某一点,而结束于另一点,不用扫描全部索引。
6)index:Full Index Scan ,index与ALL的区别为index类型只遍历索引树,这通常比ALL快,因为索引文件通常比数据文件小。 (也就是说虽然ALL和index都是读全表, 但index是从索引中读取的,而ALL是从硬盘读取的)。
7)All:Full Table Scan,遍历全表获得匹配的行。
(5)Possible_keys
显示可能应用在这张表中的索引,一个或多个。 查询涉及到的字段上若存在索引,则该索引将被列出,但不一定被查询实际使用。
(6)key
实际使用的索引。如果为NULL,则没有使用索引。
查询中若出现了覆盖索引,则该索引仅出现在key列表中。
(7)key_len
表示索引中使用的字节数,可通过该列计算查询中使用的索引的长度。在不损失精度的情况下,长度越短越好。
key_len显示的值为索引字段的最大可能长度,并非实际使用长度,即key_len是根据表定义计算而得,不是通过表内检索出的。
(8)ref
显示索引的哪一列被使用了,哪些列或常量被用于查找索引列上的值。
(9)rows
根据表统计信息及索引选用情况,大致估算出找到所需记录多需要读取的行数。
(10)Extra
包含不适合在其他列中显示但十分重要的额外信息:
1)Using filesort: 说明MySQL会对数据使用一个外部的索引排序,而不是按照表内的索引顺序进行读取。MySQL中无法利用索引完成的排序操作称为“文件排序”。
2)Using temporary: 使用了临时表保存中间结果,MySQL在对查询结果排序时使用临时表。常见于排序order by和分组查询group by。
3)Using index: 表示相应的SELECT操作中使用了覆盖索引(Covering Index),避免访问了表的数据行,效率不错。 如果同时出现using where,表明索引被用来执行索引键值的查找; 如果没有同时出现using where,表明索引用来读取数据而非执行查找动作覆盖索引(Covering Index):理解方式1:SELECT的数据列只需要从索引中就能读取到,不需要读取数据行,MySQL可以利用索引返回SELECT列表中 的字段,而不必根据索引再次读取数据文件,换句话说查询列要被所建的索引覆盖 理解方式2:索引是高效找到行的一个方法,但是一般数据库也能使用索引找到一个列的数据,因此他不必读取整个行。 毕竟索引叶子节点存储了他们索引的数据;当能通过读取索引就可以得到想要的数据,那就不需要读取行了,一个索引 包含了(覆盖)满足查询结果的数据就叫做覆盖索引 注意: 如果要使用覆盖索引,一定要注意SELECT列表中只取出需要的列,不可SELECT , 因为如果所有字段一起做索引会导致索引文件过大查询性能下降。
4)impossible where: WHERE子句的值总是false,不能用来获取任何元组。
5)select tables optimized away: 在没有GROUP BY子句的情况下基于索引优化。MIN/MAX操作或者对于MyISAM存储引擎优化COUNT()操作, 不必等到执行阶段再进行计算,查询执行计划生成的阶段即完成优化。
6)distinct: 优化distinct操作,在找到第一匹配的元祖后即停止找同样值的操作。
Springboot底层实现自动配置的步骤是:
(1)springboot应用启动;
(2)@SpringBootApplication起作用;
(3) @EnableAutoConfiguration;
(4)@AutoConfigurationPackage:这个组合注解主要是@Import(AutoConfigurationPackages.Registrar.class),它通过将Registrar类导入到容器中,而Registrar类作用是扫描主配置类同级目录以及子包,并将相应的组件导入到springboot创建管理的容器中;
(5)@Import(AutoConfigurationImportSelector.class):它通过将AutoConfigurationImportSelector类导入到容器中,AutoConfigurationImportSelector类作用是通过selectImports方法执行的过程中,会使用内部工具类SpringFactoriesLoader,查找classpath上所有jar包中的META-INF/spring.factories进行加载,实现将配置类信息交给SpringFactory加载器进行一系列的容器创建过程 。
Starter
springboot常用的starter
spring-boot-starter-web:嵌入tomcat和web开发需要servlet与jsp支持。
spring-boot-starter-data-jpa:数据库支持。
spring-boot-starter-data-redis : redis数据库支持。
spring-boot-starter-data-solr: solr支持。
mybatis-spring-boot-starter: 第三方的mybatis集成starter。
Spring Boot提供了两种常用的配置文件:
(1)properties文件。
(2)yml文件。
Spring boot 核心的两个配置文件:
bootstrap (. yml 或者 . properties):
boostrap 由父 ApplicationContext 加载的,比 applicaton 优先加载,且 boostrap 里面的属性不能被覆盖;
application (. yml 或者 . properties):
用于 spring boot 项目的自动化配置。
Spring Boot提供了两种常用的配置文件,分别是properties文件和yml文件。相对于properties文件而言,yml文件更年轻,也有很多的坑。可谓成也萧何败萧何,yml通过空格来确定层级关系,使配置文件结构跟清晰,但也会因为微不足道的空格而破坏了层级关系。
配置文件有 . properties
格式和 . yml
格式,它们主要的区别是书法风格不同。
. properties
配置如下:
spring. RabbitMQ. port=5672
. yml
配置如下:
spring:
RabbitMQ:
port: 5672
. yml
格式不支持 @PropertySource 注解导入。
通过spring注解@Value("${xxx}")的方法注入到全局变量中即可读取配置文件中的参数。
Dubbo支持dubbo、rmi、hessian、http、webservice、thrift、redis等多种协议,但是Dubbo官网是推荐我们使用Dubbo协议的。
(1)dubbo 协议 (默认)
1)dubbo 缺省协议采用单一长连接和NIO异步通讯,适合于小数据量大并发的服务调用,以及服务消费者机器数远大于服务提供者机器数的情况。
2)不适合传送大数据量的服务,比如传文件,传视频等,除非请求量很低。
缺省协议,使用基于mina1.1.7+hessian3.2.1的tbremoting交互。
特性
连接个数:单连接
连接方式:长连接
传输协议:TCP
传输方式:NIO异步传输
序列化:Hessian 二进制序列化
适用范围:传入传出参数数据包较小(建议小于100K),消费者比提供者个数多,单一消费者无法压满提供者,尽量不要用dubbo协议传输大文件或超大字符串。
适用场景:常规远程服务方法调用。
配置
<!--配置协议: -->
<dubbo:protocol name="dubbo" port="20880" />
<!--设置默认协议: -->
<dubbo:provider protocol="dubbo" />
<!-- 设置服务协议: -->
<dubbo:service protocol="dubbo" />
<!-- 多端口 -->
<dubbo:protocol id="dubbo1" name="dubbo" port="20880" />
<dubbo:protocol id="dubbo2" name="dubbo" port="20881" />
<!-- 配置协议选项: -->
<dubbo:protocol name=“dubbo” port=“9090” server=“netty” client=“netty” codec=“dubbo”
serialization=“hessian2” charset=“UTF-8” threadpool=“fixed” threads=“100”
queues=“0” iothreads=“9” buffer=“8192” accepts=“1000” payload=“8388608” />
3)Dubbo协议缺省每服务每提供者每消费者使用单一长连接,如果数据量较大,可以使用多个连接。
<dubbo:protocol name="dubbo" connections="2" />
<dubbo:service connections=”0”>或<dubbo:reference connections=”0”>表示该服务使用JVM共享长连接。(缺省)
<dubbo:service connections=”1”>或<dubbo:reference connections=”1”>表示该服务使用独立长连接。
<dubbo:service connections=”2”>或<dubbo:reference connections=”2”>表示该服务使用独立两条长连接。
4)为防止被大量连接撑挂,可在服务提供方限制大接收连接数,以实现服务提供方自我保护。
<dubbo:protocol name="dubbo" accepts="1000" />
为什么要消费者比提供者个数多?
因dubbo协议采用单一长连接,假设网络为千兆网卡(1024Mbit=128MByte),根据测试经验数据每条连接最多只能压满7MByte(不同的环境可能不一样,供参考),理论上1个服务提供者需要20个服务消费者才能压满网卡。
为什么不能传大包?
因dubbo协议采用单一长连接,如果每次请求的数据包大小为500KByte,假设网络为千兆网卡(1024Mbit=128MByte),每条连接最大7MByte(不同的环境可能不一样,供参考),单个服务提供者的TPS(每秒处理事务数)最大为:128MByte / 500KByte = 262。单个消费者调用单个服务提供者的TPS(每秒处理事务数)最大为:7MByte / 500KByte = 14。如果能接受,可以考虑使用,否则网络将成为瓶颈。
为什么采用异步单一长连接?
因为服务的现状大都是服务提供者少,通常只有几台机器,而服务的消费者多,可能整个网站都在访问该服务,比如Morgan的提供者只有6台提供者,却有上百台消费者,每天有1.5亿次调用,如果采用常规的hessian服务,服务提供者很容易就被压跨,通过单一连接,保证单一消费者不会压死提供者,长连接,减少连接握手验证等,并使用异步IO,复用线程池,防止C10K问题。
接口增加方法,对客户端无影响,如果该方法不是客户端需要的,客户端不需要重新部署;
输入参数和结果集中增加属性,对客户端无影响,如果客户端并不需要新属性,不用重新部署;
输入参数和结果集属性名变化,对客户端序列化无影响,但是如果客户端不重新部署,不管输入还是输出,属性名变化的属性值是获取不到的。
总结:服务器端 和 客户端 对 领域对象 并不需要完全一致,而是按照最大匹配原则。
如果不是集成Spring,单独配置如下:
dubbo.service.protocol=dubbo
(2)rmi 协议
1)RMI协议采用JDK标准的java.rmi.*实现,采用阻塞式短连接和JDK标准序列化方式 。
如果正在使用RMI提供服务给外部访问(公司内网环境应该不会有攻击风险),同时应用里依赖了老的common-collections包(dubbo不会依赖这个包,请排查自己的应用有没有使用)的情况下,存在反序列化安全风险。
请检查应用:
将commons-collections3 请升级到3.2.2版本:
https://commons.apache.org/proper/commons-collections/release_3_2_2.html
将commons-collections4 请升级到4.1版本:
https://commons.apache.org/proper/commons-collections/release_4_1.html
新版本的commons-collections解决了该问题 。
特性
连接个数:多连接
连接方式:短连接
传输协议:TCP
传输方式:同步传输
序列化:Java标准二进制序列化
适用范围:传入传出参数数据包大小混合,消费者与提供者个数差不多,可传文件。
适用场景:常规远程服务方法调用,与原生RMI服务互操作。
接口
如果服务接口继承了java.rmi.Remote接口,可以和原生RMI互操作,即:
提供者用Dubbo的RMI协议暴露服务,消费者直接用标准RMI接口调用,或者提供方用标准RMI暴露服务,消费方用Dubbo的RMI协议调用。
如果服务接口没有继承java.rmi.Remote
接口,缺省Dubbo将自动生成一个com.xxx.XxxService$Remote
的接口,并继承java.rmi.Remote
接口,并以此接口暴露服务,
但如果设置了
,将不生成$Remote接口,而使用Spring的RmiInvocationHandler接口暴露服务,和Spring兼容。
配置
<!-- 定义 RMI 协议: -->
<dubbo:protocol name="rmi" port="1099" />
<!--设置默认协议: -->
<dubbo:provider protocol="rmi" />
<!-- 设置服务协议: -->
<dubbo:service protocol="rmi" />
<!--多端口: -->
<dubbo:protocol id="rmi1" name="rmi" port="1099" />
<dubbo:protocol id="rmi2" name="rmi" port="2099" />
<dubbo:service protocol="rmi1" />
<!--Spring 兼容性:-->
<dubbo:protocol name="rmi" codec="spring" />
(3)hessian 协议
Hessian 协议用于集成 Hessian 的服务,Hessian 底层采用 Http 通讯,采用 Servlet 暴露服务,Dubbo 缺省内嵌 Jetty 作为服务器实现。
Dubbo 的 Hessian 协议可以和原生 Hessian 服务互操作,即:
提供者用 Dubbo 的 Hessian 协议暴露服务,消费者直接用标准 Hessian 接口调用
或者提供方用标准 Hessian 暴露服务,消费方用 Dubbo 的 Hessian 协议调用。
Hessian 是 Caucho 开源的一个 RPC 框架,其通讯效率高于 WebService 和 Java 自带的序列化。
特性
连接个数:多连接
连接方式:短连接
传输协议:HTTP
传输方式:同步传输
序列化:Hessian二进制序列化
适用范围:传入传出参数数据包较大,提供者比消费者个数多,提供者压力较大,可传文件。
适用场景:页面传输,文件传输,或与原生hessian服务互操作。
依赖:
<dependency>
<groupId>com.caucho</groupId>
<artifactId>hessian</artifactId>
<version>4.0.7</version>
</dependency>
约束
1)参数及返回值需实现Serializable接口。
2)参数及返回值不能自定义实现List, Map, Number, Date, Calendar等接口,只能用JDK自带的实现,因为hessian会做特殊处理,自定义实现类中的属性值都会丢失。
配置
<!-- 定义 hessian 协议: -->
<dubbo:protocol name="hessian" port="8080" server="jetty" />
<!--设置默认协议: -->
<dubbo:provider protocol="hessian" />
<!-- 设置 service 协议: -->
<dubbo:service protocol="hessian" />
<!-- 多端口:-->
<dubbo:protocol id="hessian1" name="hessian" port="8080" />
<dubbo:protocol id="hessian2" name="hessian" port="8081" />
<!--直连:-->
<dubbo:reference id="helloService" interface="HelloWorld" url="hessian://10.20.153.10:8080/helloWorld" />
web.xml 配置:
<servlet>
<servlet-name>dubbo</servlet-name>
<servlet-class>com.alibaba.dubbo.remoting.http.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dubbo</servlet-name>
<url-pattern>/*
注意:如果使用servlet派发请求
协议的端口
必须与servlet容器的端口相同,协议的上下文路径
必须与servlet应用的上下文路径相同。
(4)http 协议
基于http表单的远程调用协议。
特性
连接个数:多连接
连接方式:短连接
传输协议:HTTP
传输方式:同步传输
序列化:表单序列化 ,即 json
适用范围:传入传出参数数据包大小混合,提供者比消费者个数多,可用浏览器查看,可用表单或URL传入参数,暂不支持传文件。
适用场景:需同时给应用程序和浏览器JS使用的服务。
配置
<!-- 配置协议:-->
<dubbo:protocol name="http" port="8080" />
<!-- 配置 Jetty Server (默认):-->
<dubbo:protocol ... server="jetty" />
<!-- 配置 Servlet Bridge Server (推荐使用): -->
<dubbo:protocol ... server="servlet" />
配置 DispatcherServlet:
<servlet>
<servlet-name>dubbo</servlet-name> <servlet-class>org.apache.dubbo.remoting.http.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dubbo</servlet-name>
<url-pattern>/*
注意:如果使用 servlet 派发请求 :
协议的端口
必须与servlet容器的端口相同,协议的上下文路径
必须与servlet应用的上下文路径相同。
(5)webservice 协议
基于 WebService 的远程调用协议,基于 Apache CXF的 frontend-simple 和 transports-http 实现。
可以和原生 WebService 服务互操作,即:提供者用 Dubbo 的 WebService 协议暴露服务,消费者直接用标准 WebService 接口调用,或者提供方用标准 WebService 暴露服务,消费方用 Dubbo 的 WebService 协议调用。
依赖
<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-rt-frontend-simple</artifactId>
<version>2.6.1</version>
</dependency>
<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-rt-transports-http</artifactId>
<version>2.6.1</version>
</dependency>
特性
连接个数:多连接
连接方式:短连接
传输协议:HTTP
传输方式:同步传输
序列化:SOAP文本序列化
适用场景:系统集成,跨语言调用
1)基于CXF的 frontend-simple 和 transports-http 实现。
2) CXF是Apache开源的一个RPC框架:http://cxf.apache.org,由Xfire和Celtix合并而来 。
可以和原生WebService服务互操作,即:
提供者用Dubbo的WebService协议暴露服务,消费者直接用标准WebService接口调用,或者提供方用标准WebService暴露服务,消费方用Dubbo的WebService协议调用。
约束
参数及返回值需实现Serializable接口
参数尽量使用基本类型和POJO。
配置
<!-- 配置协议: -->
<dubbo:protocol name="webservice" port="8080" server="jetty" />
<!-- 配置默认协议:-->
<dubbo:provider protocol="webservice" />
<!-- 配置服务协议:-->
<dubbo:service protocol="webservice" />
<!--多端口: -->
<dubbo:protocol id="webservice1" name="webservice" port="8080" />
<dubbo:protocol id="webservice2" name="webservice" port="8081" />
<!-- 直连: -->
<dubbo:reference id="helloService" interface="HelloWorld" url="webservice://10.20.153.10:8080/com.foo.HelloWorld" />
<!-- WSDL -->
http://10.20.153.10:8080/com.foo.HelloWorld?wsdl
<!-- Jetty Server: (默认) -->
<dubbo:protocol ... server="jetty" />
<!-- Servlet Bridge Server: (推荐) -->
<dubbo:protocol ... server="servlet" />
配置 DispatcherServlet:
<servlet>
<servlet-name>dubbo</servlet-name> <servlet-class>com.alibaba.dubbo.remoting.http.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dubbo</servlet-name>
<url-pattern>/*
注意:如果使用servlet派发请求:
协议的端口
必须与servlet容器的端口相同,
协议的上下文路径
必须与servlet应用的上下文路径相同。
(6)thrift 协议
当前 dubbo 支持的 thrift 协议是对 thrift 原生协议 [2] 的扩展,在原生协议的基础上添加了一些额外的头信息,比如 service name,magic number 等。
使用 dubbo thrift 协议同样需要使用 thrift 的 idl compiler 编译生成相应的 java 代码,后续版本中会在这方面做一些增强。
依赖
<dependency>
<groupId>org.apache.thrift</groupId>
<artifactId>libthrift</artifactId>
<version>0.8.0</version>
</dependency>
配置
<dubbo:protocol name="thrift" port="3030" />
常见问题
Thrift不支持null值,不能在协议中传null。
(7)memcached 协议
基于 memcached实现的 RPC 协议。
注册 memcached 服务的地址
RegistryFactory registryFactory = ExtensionLoader.getExtensionLoader(RegistryFactory.class).getAdaptiveExtension();
Registry registry = registryFactory.getRegistry(URL.valueOf("zookeeper://10.20.153.10:2181"));
registry.register(URL.valueOf("memcached://10.20.153.11/com.foo.BarService?category=providers&dynamic=false&application=foo&group=member&loadbalance=consistenthash"));
在客户端引用
在客户端使用 :
<dubbo:reference id="cache" interface="http://10.20.160.198/wiki/display/dubbo/java.util.Map" group="member" />
或者点对点直连:
<dubbo:reference id="cache" interface="http://10.20.160.198/wiki/display/dubbo/java.util.Map"
url="memcached://10.20.153.10:11211" />
自定义接口:
<dubbo:reference id="cache"
interface="com.foo.CacheService" url="memcached://10.20.153.10:11211" />
方法名建议和memcached的标准方法名相同,即:get(key), set(key, value), delete(key)。
如果方法名和memcached的标准方法名不相同,则需要配置映射关系:(其中”p:xxx”为spring的标准p标签)
<dubbo:reference id="cache" interface="com.foo.CacheService"
url="memcached://10.20.153.10:11211" p:set="putFoo"
p:get="getFoo" p:delete="removeFoo" />
(8)redis 协议
基于 Redis实现的 RPC 协议。
注册 redis 服务的地址
可以通过脚本或监控中心手工填写表单注册redis服务的地址:
RegistryFactory registryFactory = ExtensionLoader.getExtensionLoader(RegistryFactory.class).getAdaptiveExtension();
Registry registry = registryFactory.getRegistry(URL.valueOf("zookeeper://10.20.153.10:2181"));
registry.register(URL.valueOf("redis://10.20.153.11/com.foo.BarService?category=providers&dynamic=false&application=foo&group=member&loadbalance=consistenthash"));
在客户端引用
在客户端使用:
<dubbo:reference id="store" interface="http://10.20.160.198/wiki/display/dubbo/java.util.Map" group="member" />
或者,点对点直连:
<dubbo:reference id="store" interface="http://10.20.160.198/wiki/display/dubbo/java.util.Map"
url="redis://10.20.153.10:6379" />
也可以使用自定义接口:
<dubbo:reference id="store" interface="com.foo.StoreService" url="redis://10.20.153.10:6379" />
方法名建议和redis的标准方法名相同,即:get(key), set(key, value), delete(key)。
如果方法名和redis的标准方法名不相同,则需要配置映射关系:(其中”p:xxx”为spring的标准p标签)
<dubbo:reference id="cache" interface="com.foo.CacheService"
url="memcached://10.20.153.10:11211"
p:set="putFoo" p:get="getFoo" p:delete="removeFoo" />
(9)rest ( 就是 RestFull)
基于标准的Java REST API——JAX-RS 2.0(Java API for RESTful Web Services的简写)实现的REST调用支持。
List
(1)For循环遍历list:
for(int i=0;i<list.size();i++){
if(list.get(i).equals("li"))
list.remove(i);
}
这是一种很常见的遍历方式,但是使用这种遍历删除元素会出现问题,原因在于删除某个元素后,list的大小发生了变化,而索引也在变化,所以会导致你在遍历的时候漏掉某些元素。比如当你删除第一个元素后,继续根据索引访问第二个元素后,因为删除的原因,后面的元素都往前移动了以为,所以实际访问的是第三个元素。因此,这种遍历方式可以用在读取元素,而不适合删除元素。
(2)增强for循环遍历list:
for(String x:list){
if(x.equals("li"))
list.remove(x);
}
这也是一种很常见的遍历方式,但是使用这种遍历删除元素也会出现问题,运行时会报ConcurrentModificationException异常。其实增强for循环是java语法糖的一种体现。增强for循环反编译得到字节码是Iterator遍历。
语法糖:
语法糖(Syntactic sugar),也译为糖衣语法,是由英国计算机科学家彼得·兰丁发明的一个术语,指计算机语言中添加的某种语法,这种语法对语言的功能没有影响,但是更方便程序员使用。语法糖让程序更加简洁,有更高的可读性。
java中的语法糖只存在于编译期, 在编译器将 .java 源文件编译成 .class 字节码时, 会进行解语法糖操作, 还原最原始的基础语法结构。这些语法糖包含条件编译、断言、Switch语句与枚举及字符串结合、可变参数、自动装箱/拆箱、枚举、内部类、泛型擦除、增强for循环、lambda表达式、try-with-resources语句、JDK10的局部变量类型推断等等。
异常是在next方法的checkForComodification中抛出,抛出的原因是modCount !=expectedModCount
。这里的modCount是指这个list对象从呢我出来到现在被修改的次数,当调用list的add或者remove方法的时候,这个modCount都会自动增减;iterator创建的时候modCount被复制给了expectedModcount,但是调用list的add和remove方法的时候不会同时自动增减expectedModcount,这样就导致两个count不相等,从而抛出异常。如果删除的是倒数第二个元素却不会碰到异常
(3)iterator遍历遍历list删除:
Iterator<String> it = list.iterator();
while(it.hasNext()){
String x = it.next();
if(x.equals("li")){
it.remove();
}
}
这种方式是可以正常遍历和删除的。但是你可能看到上面代码感觉和增强for循环内部实现的代码差不多,其实差别就在于上面使用
一个使用list.remove()
,一个使用it.remove()
。
HashMap
(1)For循环遍历HashMap删除:
for(Map.Entry
Integer key = entry.getKey();
if(key % 2 == 0){
System.out.println("del key " + key);
map.remove(key);
System.out.println(“key " + + key + " ok del”);
}
遍历删除会报ConcurrentModificationException异常。
(2)增强for遍历HashMap删除:
Set keySet = map.keySet();
for(Integer key : keySet){
if(key % 2 == 0){
System.out.println("del key " + key);
keySet.remove(key);
System.out.println(“key " + + key + " ok del”);
}
}
遍历删除会报ConcurrentModificationException异常。
(3)Iterator遍历HashMap删除:
Iterator<Map.Entry<Integer, String>> it = map.entrySet().iterator();
while(it.hasNext()){
Map.Entry<Integer, String> entry = it.next();
Integer key = entry.getKey();
if(key % 2 == 0){
System.out.println("del key " + key);
it.remove();
System.out.println("key " + + key + " ok del");
}
}
遍历无报错。
如果查询源代码以上的三种的删除方式都是通过调用HashMap.removeEntryForKey
方法来实现删除key的操作。在removeEntryForKey方法内执行删除了key,modCount就会执行一次自增操作,此时modCount就与expectedModCOunt不一致了,上面三种remove实现中,只有第三种iterator的remove方法在调用完removeEntryForKey方法后同步了expectedModCount值与modCount相同,所以iterator方式不会抛出异常。
Dubbo是Alibaba开源的分布式服务框架,它最大的特点是按照分层的方式来架构,使用这种方式可以使各个层之间解耦合(或者最大限度地松耦合)。从服务模型的角度来看, Dubbo采用的是一种非常简单的模型,要么是提供方提供服务,要么是消费方消费服务,所以基于这一点可以抽象出服务提供方(Provider)和服务消费方(Consumer)两个角色。关于注册中心、协议支持、服务监控等内容,详见后面描述。
总体架构
Dubbo的总体架构,如图所示:
Dubbo框架设计一共划分了10个层,最上面的Service层是留给实际想要使用Dubbo开发分布式服务的开发者实现业务逻辑的接口层。图中左边淡蓝背景的为服务消费方使用的接口,右边淡绿色背景的为服务提供方使用的接口, 位于中轴线上的为双方都用到的接口。
下面,结合Dubbo官方文档,我们分别理解一下框架分层架构中,各个层次的设计要点:
(1)服务接口层(Service):该层是与实际业务逻辑相关的,根据服务提供方和服务消费方的业务设计对应的接口和实现。
(2)配置层(Config):对外配置接口,以ServiceConfig和ReferenceConfig为中心,可以直接new配置类,也可以通过spring解析配置生成配置类。
(3)服务代理层(Proxy):服务接口透明代理,生成服务的客户端Stub和服务器端Skeleton,以ServiceProxy为中心,扩展接口为ProxyFactory。
(4)服务注册层(Registry):封装服务地址的注册与发现,以服务URL为中心,扩展接口为RegistryFactory、Registry和RegistryService。可能没有服务注册中心,此时服务提供方直接暴露服务。
(5)集群层(Cluster):封装多个提供者的路由及负载均衡,并桥接注册中心,以Invoker为中心,扩展接口为Cluster、Directory、Router和LoadBalance。将多个服务提供方组合为一个服务提供方,实现对服务消费方来透明,只需要与一个服务提供方进行交互。
(6)监控层(Monitor):RPC调用次数和调用时间监控,以Statistics为中心,扩展接口为MonitorFactory、Monitor和MonitorService。
(7)远程调用层(Protocol):封将RPC调用,以Invocation和Result为中心,扩展接口为Protocol、Invoker和Exporter。Protocol是服务域,它是Invoker暴露和引用的主功能入口,它负责Invoker的生命周期管理。Invoker是实体域,它是Dubbo的核心模型,其它模型都向它靠扰,或转换成它,它代表一个可执行体,可向它发起invoke调用,它有可能是一个本地的实现,也可能是一个远程的实现,也可能一个集群实现。
(8)信息交换层(Exchange):封装请求响应模式,同步转异步,以Request和Response为中心,扩展接口为Exchanger、ExchangeChannel、ExchangeClient和ExchangeServer。
(9)网络传输层(Transport):抽象mina和netty为统一接口,以Message为中心,扩展接口为Channel、Transporter、Client、Server和Codec。
(10)数据序列化层(Serialize):可复用的一些工具,扩展接口为Serialization、 ObjectInput、ObjectOutput和ThreadPool。
根据官方提供的,对于上述各层之间关系的描述,如下所示:
(1)在RPC中,Protocol是核心层,也就是只要有Protocol + Invoker + Exporter就可以完成非透明的RPC调用,然后在Invoker的主过程上Filter拦截点。
(2)图中的Consumer和Provider是抽象概念,只是想让看图者更直观的了解哪些类分属于客户端与服务器端,不用Client和Server的原因是Dubbo在很多场景下都使用Provider、Consumer、Registry、Monitor划分逻辑拓普节点,保持统一概念。
(3)而Cluster是外围概念,所以Cluster的目的是将多个Invoker伪装成一个Invoker,这样其它人只要关注Protocol层Invoker即可,加上Cluster或者去掉Cluster对其它层都不会造成影响,因为只有一个提供者时,是不需要Cluster的。
(5)Proxy层封装了所有接口的透明化代理,而在其它层都以Invoker为中心,只有到了暴露给用户使用时,才用Proxy将Invoker转成接口,或将接口实现转成Invoker,也就是去掉Proxy层RPC是可以Run的,只是不那么透明,不那么看起来像调本地服务一样调远程服务。
(6)而Remoting实现是Dubbo协议的实现,如果你选择RMI协议,整个Remoting都不会用上,Remoting内部再划为Transport传输层和Exchange信息交换层,Transport层只负责单向消息传输,是对Mina、Netty、Grizzly的抽象,它也可以扩展UDP传输,而Exchange层是在传输层之上封装了Request-Response语义。
(7)Registry和Monitor实际上不算一层,而是一个独立的节点,只是为了全局概览,用层的方式画在一起。
(1)服务注册与发现——Netflix Eureka
(2)负载均衡——Netflix Ribbon
(3)断路器——Netflix Hystrix
(4)服务网关——Netflix Zuul
(5)分布式配置——Spring Cloud Config
(6)Feign:基于动态代理机制,根据注解和选择的机器,拼接请求 url 地址,发起请求。
简介
Feign是Netflix开发的声明式、模板化的HTTP客户端, Feign可以帮助我们更快捷、优雅地调用HTTP API。
在Spring Cloud中,使用Feign非常简单——创建一个接口,并在接口上添加一些注解,代码就完成了。Feign支持多种注解,例如Feign自带的注解或者JAX-RS注解等。
Spring Cloud对Feign进行了增强,使Feign支持了Spring MVC注解,并整合了Ribbon和Eureka,从而让Feign的使用更加方便。
Spring Cloud Feign是基于Netflix feign实现,整合了Spring Cloud Ribbon和Spring Cloud Hystrix,除了提供这两者的强大功能外,还提供了一种声明式的Web服务客户端定义的方式。
实现步骤:
(1)导入依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!--由于feign整合了ribbon的负载均衡,所以需要引入ribbon的依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
<version>2.1.1.RELEASE</version>
</dependency>
<!--需要从eureka拉取服务-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-netflix-eureka-client</artifactId>
<version>2.1.1.RELEASE</version>
</dependency>
(2)启动类添加注解支持
@EnableFeignClients
(3)客户端编写
//定义接口
package com.sun.client;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
@FeignClient(value = "user-service") //声明这是feign的客户端
public interface UserFeignClient {
@GetMapping("user")
public String getUser();
}
注释:
这是一个接口,Feign会通过动态代理,帮我们生成实现类。这点跟mybatis的mapper很像@FeignClient,声明这是一个Feign客户端,类似@Mapper注解。同时通过value属性指定服务名称接口中的定义方法,完全采用SpringMVC的注解,Feign会根据注解帮我们生成URL,并访问获取结果改造原来的调用逻辑,不再调用UserDao:
//实现调用
@Autowired
private UserFeignClient userFeignClient;
@GetMapping("feign")
public Object feign(){
return userFeignClient.getUser();
}
(4)feign自动实现负载均衡
(5)feign的重试
#feign的重试与ribbon的配置相同,只要添加相应的配置即可。
client:
ribbon:
MaxAutoRetries: 1 #配置首台服务器重试1次
MaxAutoRetriesNextServer: 2#配置其他服务器重试两次
ConnectTimeout: 500 #链接超时时间
ReadTimeout: 2000 #请求处理时间
OkToRetryOnAllOperations: true #每个操作都开启重试机制
(6)feign与hystrix的集成配置熔断机制
1)添加熔断配置
feign:
hystrix:
enabled: true #允许熔断
2)添加熔断处理类
实现 UserFeignClient接口
3)实现调用
Requestmaping方式调用
幂等性(如何避免消息的重复消费)
概述:可能因为各种原因,导致了生产端发送了多条一样的消息给消费端,但是,消费端也只能消费一条,不会多消费。
(1)唯一ID + 指纹码机制
指纹码(就是时间戳 + 业务的一些规则, 来保证id + 指纹码在同一时刻是唯一的,不会出现重复)
唯一ID + 指纹码机制,利用数据库主键去重;
select count(1) from t_order where id = 唯一ID + 指纹码;
好处:实现简单;
坏处:高并发下有数据库写入的瓶颈;
解决方案:跟进ID进行分库分表进行算法路由;
(2)利用Redis的原子性实现
存在的问题:
我们是否需要进行数据的持久化,若需要持久化,怎么保证数据库和redis做到原子性?
若不立即持久化,都存储到redis中,如何设置同步策略?
(3)设置前置条件
加一个版本号控制。
消息队列出现错误消息怎么办?
RabbitMQ简介
AMQP,即Advanced Message Queuing Protocol,高级消息队列协议,是应用层协议的一个开放标准,为面向消息的中间件设计。消息中间件主要用于组件之间的解耦,消息的发送者无需知道消息使用者的存在,反之亦然。
AMQP的主要特征是面向消息、队列、路由(包括点对点和发布/订阅)、可靠性、安全。
RabbitMQ是一个开源的AMQP实现,服务器端用Erlang语言编写,支持多种客户端,如:Python、Ruby、.NET、Java、JMS、C、PHP、ActionScript、XMPP、STOMP等,支持AJAX。用于在分布式系统中存储转发消息,在易用性、扩展性、高可用性等方面表现不俗。
发送方确认消息机制
采用最终一致性原理
需要保证以下三要素:
a、确保生产者一定要将数据投递到MQ服务器中(采用MQ消息确认机制)
b、确保消费者能够正确消费消息,采用手动ACK模式(注意重试、幂等性问题)
c、如何保证第一个事务先执行,采用补偿机制,在创建一个补单消费者进行监听,如果订单没有创建成功,进行补单。(如果第一个事务中出错,补单消费者会在重新执行一次第一个事务,例如第一个事务是添加订单表,如果失败在补单的时候重新生成订单记录,由于订单号唯一,所以不会重复)
@Transactional,表示该类受Spring事务管理。如果该类中每个方法不需要事务管理,则在该方法前加入下面的Java代码 :
@Transactional(propagation=Propagation.NOT_SUPPORTED)
fail-fast 机制是java集合(Collection)中的一种错误机制。当多个线程对同一个集合的内容进行操作时,就可能会产生fail-fast事件。
例如:当某一个线程A通过iterator去遍历某集合的过程中,若该集合的内容被其他线程所改变了;那么线程A访问集合时,就会抛出ConcurrentModificationException异常,产生fail-fast事件。
简单来说是java为了防止出现并发异常的一个机制,但是其实在单线程下也可以产生。
多线程操作list的话建议使用CopyOnWriteArrayList,或者对迭代器加锁。