搭建未用到springcloud的微服务架构,同昨天
浏览器访问测试:http://localhost:5010/user/consumer/8
同昨天(新建工程 导包 配置 启动类 注解)
浏览器访问测试:http://localhost:1010
同昨天(导包 配置 注解)
重启生产者,浏览器访问测试:http://localhost:4010/user/provider/3
tips:将eureka页面中显示的实例服务的计算机名称改为ip
同昨天(导包 配置 注解)
UserConsumerController
/**
* 服务的消费者
*/
@RestController
@RequestMapping("/user/consumer")
public class UserConsumerController {
// private static String URL_PRIFIX = "http://localhost:4010";
@Autowired
private RestTemplate restTemplate; // 用于发起远程调用的工具
@Autowired
private DiscoveryClient client;
// 返回一个用户
@RequestMapping("/{id}")
public User getUser(@PathVariable("id") Long id){
List<ServiceInstance> list = client.getInstances("user-provider");
// 获取第一台机器
ServiceInstance serviceInstance = list.get(0);
// 获取第一台机器的ip
String ip = serviceInstance.getHost();
// 获取第一台机器的端口
int port = serviceInstance.getPort();
String url = "http://"+ip+":"+port+"/user/provider/" + id;
// 根据id发起远程调用4010获取用户
// return restTemplate.getForObject(URL_PRIFIX + "/user/provider/" + id,User.class);
return restTemplate.getForObject(url,User.class);
}
}
重启消费者,浏览器访问测试:http://localhost:5010/user/consumer/44
防止eureka单点故障
见文档6
application.yml备份一份单机版本,复制多份集群配置版本
如application-eureka1.yml
server:
port: 1010
eureka:
instance:
hostname: eureka1
client:
registerWithEureka: false #是否要注册到eureka
fetchRegistry: false #表示是否从Eureka Server获取注册信息
serviceUrl:
defaultZone: http://eureka1:1010/eureka,http://eureka2:1020/eureka,http://eureka3:1030/eureka
spring:
application:
name: eureka1
外网无法访问http://eureka1:1010/eureka,需要本机配置虚拟域名
C:\Windows\System32\drivers\etc\hosts
关闭所有项目,修改application.yml
spring:
profiles:
active: eureka1
设置idea编辑该启动类配置支持多实例启动
active分别写eureka1、eureka2、eureka3启动后会找到以对应后缀的文件共同启动
浏览器访问测试:http://localhost:1010,http://localhost:1020,http://localhost:1030
设置把自己也注册进去
删掉或注释以下两行即可
# registerWithEureka: false #是否要注册到eureka
# fetchRegistry: false #表示是否从Eureka Server获取注册信息
active分别写eureka1、eureka2、eureka3启动后会找到以对应后缀的文件共同启动
tips:前两个启动报错正常,因为配置里的其他eureka还没启动,不用管,最后一个启动就不会报错了
浏览器访问测试:http://localhost:1010,http://localhost:1020,http://localhost:1030
生产环境把jar包部署多个服务器就ok了,但是现在是开发阶段同一台主机不同端口号来代替服务器 ,方案有两种。
方案1:拷贝一份代码为多份,修改端口不一样。分别启动
方案2:一份代码,多份配置。 每一个配置启动一份。
我们这里用第二种
application.yml备份一份单机版本,复制多份集群配置版本
如application-pro1.yml
server:
port: 4010
spring:
application:
name: user-provider
eureka:
client:
service-url:
defaultZone: http://eureka1:1010/eureka,http://eureka2:1020/eureka,http://eureka3:1030/eureka #告诉服务提供者要把服务注册到哪儿
instance:
prefer-ip-address: true # 当调用getHostname获取实例的hostname时,返回ip而不是host名称
ip-address: 127.0.0.1 # 指定自己的ip信息,不指定的话会自己寻找
hostname: user-provider:4010
修改application.yml
spring:
profiles:
active: pro1
设置idea编辑该启动类配置支持多实例启动
active分别写pro1、pro2、pro3启动后会找到以对应后缀的文件共同启动
浏览器访问测试:http://localhost:1010
tips:spring application name一样就会单行一起展示,不一样就会多行分别展示
启动消费者服务,浏览器访问测试:http://localhost:5010/user/consumer/55
见文档7.1,7.3,7.4.1,7.4.2
拷贝user-consumer-5010为user-consumer-ribbon-5110
user-consumer-ribbon-5110的pom里修改artifactId
<artifactId>user-consumer-ribbon-5110artifactId>
父pom里增加
<module>user-consumer-ribbon-5110module>
刷新Maven
application.yml调整端口
server:
port: 5110
spring:
application:
name: user-consumer
eureka:
client:
service-url:
defaultZone: http://localhost:1010/eureka #告诉服务提供者要把服务注册到哪儿
instance:
prefer-ip-address: true # 当调用getHostname获取实例的hostname时,返回ip而不是host名称
ip-address: 127.0.0.1 # 指定自己的ip信息,不指定的话会自己寻找
hostname: user-consumer:5110
删除target文件夹
导包
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-ribbonartifactId>
dependency>
获取RestTemplate加注解
@Configuration
public class HttpUtils {
@Bean
@LoadBalanced //开启ribbon的负载均衡
public RestTemplate getRestTemplate(){
return new RestTemplate();
}
}
修改UserConsumerController
@RestController
@RequestMapping("/user/consumer")
public class UserConsumerController {
private static String URL_PRIFIX = "http://USER-PROVIDER";
@Autowired
private RestTemplate restTemplate; // 用于发起远程调用的工具
@Autowired
private DiscoveryClient client;
// 返回一个用户
@RequestMapping("/{id}")
public User getUserById(@PathVariable("id") Long id){
// 开启ribbon的负载均衡,使用服务名字调用
User user = restTemplate.getForObject(URL_PRIFIX + "/user/provider/" + id, User.class);
return user;
}
}
服务提供者加代码以方便看是哪一个提供者提供的服务
UserProviderController
@RestController
@RequestMapping("/user/provider")
public class UserProviderController {
@Autowired
private ApplicationContext context;
// 返回一个用户
@RequestMapping("/{id}")
public User getUser(@PathVariable("id") Long id) {
// 为了方便看是哪一个提供者提供的服务
String activeProfile = context.getEnvironment().getActiveProfiles()[0];
return new User(id, "zs," + activeProfile);
}
}
分别启动eureka集群,生产者集群和用了ribbon的消费者服务
浏览器访问测试:http://localhost:5110/user/consumer/5110,不断刷新会出现轮询效果
见文档7.4.4
内置策略配置bean
@Configuration
public class HttpUtils {
@Bean
@LoadBalanced //开启ribbon的负载均衡
public RestTemplate getRestTemplate(){
return new RestTemplate();
}
// 配置负载均衡策略,即返回一个IRule对象交给spring管理
@Bean
public IRule getRule(){
// 随机选择一个可用的服务器的策略
return new RandomRule();
}
}
自定义策略重写IRule接口实现类重写对应方法
略
浏览器刷新访问测试:http://localhost:5110/user/consumer/5110
见文档7.4.5
小坑:okToRetryOnAllOperations: false,是否所有操作都重试
防止调用方使用ribbon请求超时但超时时间后被调用服务又处理成功,然后调用方认为未成功又重复请求的情况
对于添加数据请求数据库有做幂等性校验就没问题,即対某列添加唯一性约束,重复数据不会添加成功
见文档7.5.1
拷贝user-consumer-ribbon-5110为user-consumer-feign-5210
修改artifactId
父pom添加module
修改application.yml
删除target
替换ribbon包为feign包,关闭ribbon注解,ribbon配置,ribbon策略代码和feign一样不用删
添加UserClient
@FeignClient("user-provider")
@RequestMapping("/user/provider/")
public interface UserClient {
@RequestMapping("/{id}") // 通过这些参数自动生成一个代理实现类,发起远程调用
User getUser(@PathVariable("id") Long id);
}
启动类加注解
@SpringBootApplication
@EnableEurekaClient //表示是eureka的客户端
@EnableFeignClients(basePackages = "cn.ming.client") //如果是主内所在子包,可以不用加basePackages,但是最好加上
public class UserConsumerApp {
public static void main(String[] args) {
SpringApplication.run(UserConsumerApp.class,args);
}
}
修改UserConsumerController
@RestController
@RequestMapping("/user/consumer")
public class UserConsumerController {
@Autowired
private UserClient client;
// 返回一个用户
@RequestMapping("/{id}")
public User getUserById(@PathVariable("id") Long id){
System.out.println(client.getClass());//
User user = client.getUser(id);
return user;
}
}
启动eureka集群,生产者集群和此项目
浏览器刷新访问测试:http://localhost:5210/user/consumer/5210
见文档2.1,2.2
如下单前检查用户是否登录或调取用户信息,其中一台挂掉会影响请求到这台机器上的所有请求,影响越来越大,造成雪崩现象
eureka只是定时更新可调用服务列表,处理此问题不专业
Hystrix可対问题机器进行隔离,熔断,限流,降级,缓存,
隔离:单独分开
熔断:例如:每当20个请求中,有50%失败时,熔断器就会打开,此时再调用此服务,将会直接返回失败,不再调远程服务。直到5s钟之后,重新检测该触发条件,判断是否把熔断器关闭,或者继续打开。
限流:减少给此服务分发的请求数量
降级:回调返回设定的错误,避免直接没任何响应
缓存:把请求排队或转给其他服务处理
Hystrix是在远程调用是应用的,所以要搭配ribbon或feign使用
在服务提供者方向写
项目准备
拷贝user-provider-4010为user-provider-hystrix-4110
修改父pom和子pom
修改application.yml
删除target
导包
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-hystrixartifactId>
dependency>
启动类加注解
@SpringBootApplication
@EnableEurekaClient //表示是eureka的客户端
@EnableHystrix //表示添加 hystrix 服务熔断降级
public class UserProviderApp {
public static void main(String[] args) {
SpringApplication.run(UserProviderApp.class,args);
}
}
修改UserProviderController
@RestController
@RequestMapping("/user/provider")
public class UserProviderController {
@Autowired
private ApplicationContext context;
// 返回一个用户
@RequestMapping("/{id}")
@HystrixCommand(fallbackMethod = "fallbackMethod") //服务降级的处理回调的方法
public User getUser(@PathVariable("id") Long id) {
// 为了方便看是哪一个提供者提供的服务
String activeProfile = context.getEnvironment().getActiveProfiles()[0];
if (id == 2){ //模拟服务器出错
throw new RuntimeException("服务器异常1");
}
return new User(id, "zs," + activeProfile);
}
public User fallbackMethod(Long id){ //降级处理的方法
return new User(id,"服务器异常2");
}
}
启动eureka集群,带hystrix的生产者,带ribbon的消费者
浏览器访问测试:http://localhost:5110/user/consumer/2
写在消费者一边
项目准备
拷贝user-consumer-feign-5210到user-consumer-feign-hystrix-5310
修改父pom和子pom
修改application.yml
删除target文件夹
不用新加依赖,有openfeign就行
application.yml
feign:
hystrix:
enabled: true #开启熔断支持
client:
config:
remote-service: #服务名,填写default为所有服务
connectTimeout: 3000
readTimeout: 3000
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 3000
新增类UserFallBack
@Component //降级处理
public class UserFallBack implements FallbackFactory<UserClient> {
@Override
public UserClient create(Throwable throwable) {
return new UserClient() {
@Override
public User getUser(Long id) {
return new User(id,"系统繁忙,请联系管理员");
}
};
}
}
修改UserClient
@FeignClient(value = "user-provider",fallbackFactory = UserFallBack.class)
@RequestMapping("/user/provider/")
public interface UserClient {
@RequestMapping("/{id}") // 通过这些参数自动生成一个代理实现类,发起远程调用
User getUser(@PathVariable("id") Long id);
}
修改user-provider-4010的UserProviderController
@RequestMapping("/{id}")
public User getUser(@PathVariable("id") Long id) {
// 为了方便看是哪一个提供者提供的服务
String activeProfile = context.getEnvironment().getActiveProfiles()[0];
if (id == 2){ //模拟服务器出错,让hystrix搭配feign降级处理
throw new RuntimeException("服务器异常");
}
return new User(id, "zs," + activeProfile);
}
启动eureka集群,user-provider-4010和user-consumer-feign-hystrix-5310
浏览器访问测试:http://localhost:5310/user/consumer/2