1. 创建一个maven工程,作为父级。
导入所需依赖
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.0.4.RELEASE</version> <relativePath/> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> <spring-cloud.version>Finchley.SR1</spring-cloud.version><!--Greenwich.SR2 Finchley.SR1--> <mapper.starter.version>2.0.3</mapper.starter.version> <mysql.version>5.1.32</mysql.version> <pageHelper.starter.version>1.2.5</pageHelper.starter.version> </properties> <dependencyManagement> <dependencies> <!--spring-cloud spring-cloud-dependencies:综合管理spring-cloud子组件 --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>${spring-cloud.version}</version> <type>pom</type> <scope>import</scope> </dependency> <!--通用mapper启动器--> <dependency> <groupId>tk.mybatis</groupId> <artifactId>mapper-spring-boot-starter</artifactId> <version>${mapper.starter.version}</version> </dependency> <!--分页助手启动器--> <dependency> <groupId>com.github.pagehelper</groupId> <artifactId>pagehelper-spring-boot-starter</artifactId> <version>${pageHelper.starter.version}</version> </dependency> <!--mysql驱动--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>${mysql.version}</version> </dependency> </dependencies> </dependencyManagement> <dependencies> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build>
2.创建注册中心,可以选择使用初始化器创建,也可以创建maven项目,之后引入依赖即可。
依赖
<dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId> </dependency> </dependencies>
yml
server: port: 10086 #服务名 spring: application: name: eureka-server #向自己注册服务 eureka: client: service-url: defaultZone: http://127.0.0.1:10086/eureka #不向自己注册 register-with-eureka: false #服饰失效后的剔除时间 server: eviction-interval-timer-in-ms: 6000 #指定使用ip地址注册,并且指定ip #enable-self-preservation: false #关闭自我保护机制 instance: prefer-ip-address: true ip-address: 127.0.0.1
启动类
@EnableEurekaServer//服务中心 @SpringBootApplication public class EurekaServer { public static void main(String[] args) { SpringApplication.run(EurekaServer.class); } }
若不做集群处理,注册中心就已经搭建完成。
3. 创建提供者服务
引入相关依赖
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> <dependency> <groupId>tk.mybatis</groupId> <artifactId>mapper-spring-boot-starter</artifactId> </dependency> <!--服务注册--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> </dependencies>
yml
server: port: 8081 spring: application: name: user-service datasource: url: jdbc:mysql://localhost:3306/sys username: root password: sasa mybatis: type-aliases-package: cn.itcast.user.pojo #注册服务 eureka: client: service-url: defaultZone: http://127.0.0.1:10086/eureka #http://127.0.0.1:10087/eureka instance: prefer-ip-address: true ip-address: 127.0.0.1 #配置心跳续约时间 #lease-renewal-interval-in-seconds: 30 #如果90秒后还没发送请求,则证明这个服务挂了 #lease-expiration-duration-in-seconds: 90
实体类
import lombok.Data;
import tk.mybatis.mapper.annotation.KeySql;
import javax.persistence.Id;
import javax.persistence.Table;
import java.util.Date;
@Data @Table(name = "tb_user") //表名 public class User { //id @Id //声明它是主键 @KeySql(useGeneratedKeys = true)//自增,可以回显 private Long id; //用户名 private String userName; //密码 private String password; }
mapper
import tk.mybatis.mapper.common.Mapper; public interface UserMapper extends Mapper<User> { /** * 导的是 tk.mybatis.mapper.common.Mapper 这个包 * 内置关于user的增删改查方法 */ }
service
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @Service public class UserService { @Autowired private UserMapper userMapper; /** * 查询 * @param id * @return */ public User queryById(long id){ return userMapper.selectByPrimaryKey(id); } /** * 新增 * @param user */ @Transactional public void insertUser(User user){ userMapper.insert(user); } /** * 修改 * @param user * @return */ @Transactional public int updateById(User user){ return userMapper.updateByPrimaryKey(user); } /** * 删除 * @param user * @return */ @Transactional public int deleteById(User user){ return userMapper.deleteByPrimaryKey(user.getId()); }
web
@Slf4j @RestController /* @Controller + @ResponseBody*/ @RequestMapping("user") public class HelloController { /* @Autowired private DataSource dataSource;*/ @Autowired private UserService userService; @RequestMapping("{id}") public User hello(@PathVariable("id") Long id){ /* try { Thread.sleep(3000);//模拟超时 } catch (InterruptedException e) { e.printStackTrace(); }*/ return userService.queryById(id); } @RequestMapping("save") public void save(@RequestBody User user){ userService.insertUser(user); } @RequestMapping("delete") public int delete(@RequestBody User user){ return userService.deleteById(user); } @RequestMapping("update") public int update(@RequestBody User user){ return userService.updateById(user); }
4. 创建消费者服务
依赖
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> </dependencies>
yml
server: port: 8088 #服务名 spring: application: name: consumer-servies #注册服务 eureka: client: service-url: defaultZone: http://127.0.0.1:10086/eureka #配置消费方拉取服务列表周期时间 ,http://127.0.0.1:10087/eureka registry-fetch-interval-seconds: 3 instance: prefer-ip-address: true ip-address: 127.0.0.1
创建实体,主要用于接收返回值。
@Data public class User { //id private Long id; //用户名 private String userName; //密码 private String password; }
暂时先不使用feign框架,使用spring的restTemplate,在来感受一下feign的魅力。
所以在启动类需要注入restTemplate
@EnableDiscoveryClient//扫描注册的实例服务 @SpringBootApplication public class ConsumerApplication { @Bean @LoadBalanced public RestTemplate testTemplate(){ return new RestTemplate(); } public static void main(String[] args) { SpringApplication.run(ConsumerApplication.class); } }
web
@RestController @RequestMapping("consumer") public class ConsumerController { //用于跨域调用,使用feign框架之后不在需要这么复杂的写法。 @Autowired private RestTemplate restTemplate; @GetMapping("{id}") public User queryById(@PathVariable("id") Long id){ String url = "http://user-service/user/"+id; return restTemplate.getForObject(url, User .class); } @PostMapping("save") public void save(@RequestBody User user){ String url = "http://user-service/user/save"; restTemplate.postForEntity(url,user,null); } @PutMapping("update") public Integer update(@RequestBody User user){ String url = "http://user-service/user/update"; return restTemplate.postForObject(url,user, Integer.class); } @DeleteMapping("{id}") public Integer delete(@PathVariable("id") Long id){ String url = "http://user-service/user/delete"; return restTemplate.getForObject(url,Integer.class); } }
到此为止,CRUD已经完成。
熔断器Hystrix是容错管理工具,作用是通过隔离、控制服务从而对延迟和故障提供更强大的容错能力,避免整个系统别拖垮。
复杂分布式架构通常都具有很多依赖,当一个应用高度耦合其他服务时非常危险且容易导致失败,这种失败很容易伤害服务的调用者,最后导致一个接一个的连续错误,,这种失败很容易伤害服务的调用者,最后导致一个接一个发生延迟,将会在数秒内导致所有应用资源被耗尽。如何处理这些问题是有关系统性能和效率的关键性问题。
当在系统高峰时期,大量对微服务的调用可能会堵塞远程服务器的线程池,如果这个线程池没有和主应用服务器的线程池隔离,就可能导致整个服务器挂机。
Hystrix使用自己的线程池,这样和主应用服务器线程池隔离,如果调用花费很长时间,会停止调用,不同的命令或命令组能够被配置使用它们各自的线程池,可以隔离不同的服务。
我们在一个tomcat中部署了多个服务,tomcat的线程容量假设为1000,这些服务相互调用调用,当一个请求过来时,其中一个服务挂了,那么线程就会被堵在这里,这时,又有其他请求过来,线程依然被堵在这里,当线程被占满而又无法释放时,整个系统就会崩掉。
线程隔离:分配给每隔服务单独的线程,假设50个,这样就不会堵塞所有的线程。但是但这50时个线程也全都阻塞了改怎么办呢?
服务降级:当单独分配的线程也别占满时,那么其中的的线程如果超过一定的时间还没被响应,就返回一个自定义的失败响应,线程释放。请求访问不到原本想要服务时,返回一个自定的响应服务。直到请求成功,服务端才会继续提供这个服务,称为服务降级,相当于是一种力度降级,不再提供整套服务。超时以后返回友好错误信息也可称为服务降级。
优先保证核心服务,而非核心服务不可用或弱可用。
服务降级虽然会导致请求失败,但是不会导致阻塞,而且最多会影响这依赖服务对应的线程池中的资源,对其他服务没有影响
触发Hystix服务降级的情况:
线程池已满
请求超时
应当是在消费方进行操作
1.在消费方引入依赖
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix</artifactId> </dependency
2. 在启动类上加上注解
@EnableCircuitBreaker//服务熔断和服务降级、
/*@EnableCircuitBreaker//服务熔断和服务降级 @SpringCloudApplication //该注解可代替以上三个注解 @EnableFeignClients //利用feign框架实现简答的远程调用
*/
//使用 SpringCloudApplication 代替以上三个注册
@SpringCloudApplication //该注解可代替以上三个注解 public class ConsumerApplication { @Bean @LoadBalanced public RestTemplate testTemplate(){ return new RestTemplate(); } public static void main(String[] args) { SpringApplication.run(ConsumerApplication.class); } }
3.配置默认超时时长,暂时不配置,使用默认的配置。
4. 修改web,编写失败返回信息,这里先只对查询做操作
@GetMapping("{id}") @HystrixCommand(fallbackMethod = "queryByIdFallBack")//无需在指定方法,只需要开启就行 public String queryById(@PathVariable("id") Long id){ String url = "http://user-service/user/"+id; return restTemplate.getForObject(url, String.class); } //成功方法和失败方法的返回值必须一致 public String queryByIdFallBack(Long id){ return "不好意思,服务器繁忙"; }
5.测试,可以在提供者服务的查询方法里设置休眠。
@GetMapping("{id}") public User hello(@PathVariable("id") Long id){ try { Thread.sleep(3000);//模拟超时 } catch (InterruptedException e) { e.printStackTrace(); } return userService.queryById(id); }
6 .针对所哟请求都开启服务降级处理。只需要要在类上加上@DefaultProperties 注解,并在每隔方法上开启服务降级。
@RestController @RequestMapping("consumer") @DefaultProperties(defaultFallback = "defaultFallback")//为这个类声明一个默认的降级方法 public class ConsumerController { @GetMapping("{id}") @HystrixCommand//无需在指定方法,只需要开启就行 public String queryById(@PathVariable("id") Long id){ String url = "http://user-service/user/"+id; return restTemplate.getForObject(url, String.class); } @PostMapping("save") @HystrixCommand public void save(@RequestBody User user){ String url = "http://user-service/user/save"; restTemplate.postForEntity(url,user,null); } //通用方法不能有参数,因为他多对很多方法做处理 public String queryByIdFallBack(){ return "不好意思,服务器繁忙"; } @PutMapping("update") @HystrixCommand public Integer update(@RequestBody User user){ String url = "http://user-service/user/update"; return restTemplate.postForObject(url,user, Integer.class); } @DeleteMapping("{id}") @HystrixCommand public Integer delete(@PathVariable("id") Long id){ String url = "http://user-service/user/delete"; return restTemplate.getForObject(url,Integer.class); } }
7. 优化,对超时时长进行设置,可以针对所有服务进行设置,也可以针对单独某个服务进行设置,yml文件。
#配置全局的超时时间 hystrix: command: default: execution: isolation: thread: timeoutInMilliseconds: 1000 #可以针对于某一个服务来配置超时时间 user-service: execution: isolation: thread: timeoutInMilliseconds: 1000
服务熔断:当下游的服务因为某种原因突然变得不可用或响应过慢,上游服务为了保证自己整体服务的可用性,不再继续调用目标服务,直接返回,快速释放资源。如果目标服务情况好转则恢复调用。Hystrix默认的超时时间为1s,请求时间超过1s,熔断次数加1。
熔断机制的原理很简单,像家里的电路熔断器,如果电路发生短路能立刻熔断电路,避免发生灾难。在分布式系统中应用这一模式之后,服务调用方可以自己进行判断某些服务反应慢或者存在大量超时的情况时,能够主动熔断,防止整个系统被拖垮。
不同于电路熔断只能断不能自动重连,Hystrix 可以实现弹性容错,当情况好转之后,可以自动重连。这就好比魔术师把鸽子变没了容易,但是真正考验技术的是如何把消失的鸽子再变回来。
通过断路的方式,可以将后续请求直接拒绝掉,一段时间之后允许部分请求通过,如果调用成功则回到电路闭合状态,否则继续断开。
熔断器关闭:请求可以通过
熔断器打开:请求不可以通过,迅速返回错误提示,提高服务可用性。默认情况下,如果最近的20次请求有50%都超时了,那么就会认为你的服务有问题,断路器打开。如果用户再来访问这个接口时,会快速返回失败,默认持续5秒,,5秒后进入半开状态。
半开:在进入该状态后会放入部分请求;判断请求是否成功,不成功,进入open状态,重新计时,进入halfopen状态;成功,进入closed状态。
1. 测试,修改默认阈值(请求次数),和休眠时间戳(默认5秒的时间),方便看到效果。
@GetMapping("{id}") @HystrixCommand(commandProperties = { @HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",value = "20000"),//设置请求超时 时间 @HystrixProperty(name="circuitBreaker.requestVolumeThreshold",value = "10"),//每请求10次统计一次熔错率 @HystrixProperty(name="circuitBreaker.sleepWindowInMilliseconds",value="10000"), //休眠时间(ms),默认5秒 @HystrixProperty(name="circuitBreaker.errorThresholdPercentage",value="60") }) public String queryById(@PathVariable("id") Long id){ //由我们在浏览器手动控制熔断触发,连续访问5次以上就会进入断开状态。 if(id % 2==0){ throw new RuntimeException("");//抛出异常时会触发熔断 } String url = "http://user-service/user/"+id; return restTemplate.getForObject(url, String.class); }
熔断的目的是为了提高服务的可用性,不会因为一个服务的阻塞而降低整个系统的性能。
三:引入Feign //TODO
原文:https://www.cnblogs.com/autonomy/p/12032169.html