一、demo结构
二、pom文件
(1)父工程
<parent>
<groupId>org.springframework.boot</groupId>
<version>2.2.0.RELEASE</version>
<artifactId>spring-boot-starter-parent</artifactId>
</parent>
<properties>
<spring-cloud.version>Hoxton.SR1</spring-cloud.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<scope>import</scope>
<type>pom</type>
</dependency>
</dependencies>
</dependencyManagement>
(2)注册中心
使用的是netflix-eureka-server
<dependencies>
<!-- eureka server-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
<!-- web-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- test-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- security-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
</dependencies>
(3)提供者
使用的是netflix-eureka-client
<dependencies>
<!-- eureka server-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!-- web-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- test-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
(3)消费者
使用的是netflix-eureka-client
<dependencies>
<dependency>
<groupId>com.jane</groupId>
<artifactId>service-provider</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<!-- eureka server-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!-- lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!-- web-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- test-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
二、注册中心
(1)单机
关于设置prefer-ip-address和instance-id:
如果不设置,就会直接显示电脑的名字:
如果设置启用ip注册地址,和设置好实例的名字,就可以改成 ip:端口 的形式
server:
port: 8761
spring:
application:
name: eureka-server
eureka:
instance:
hostname: eureka01 #这个主机名是指eureka注册中心的主机地址嘛?
prefer-ip-address: true #启用ip注册,到时候那个信息显示的就不是电脑地址,是ip地址
instance-id: ${spring.cloud.client.ip-address}:${server.port}
client:
register-with-eureka: false #不将自己注册到中心
fetch-registry: false #不从中心获取服务注册信息
service-url:
#暴露注册地址
defaultZone: http://localhost:8761/eureka/
#如果应用角色是 注册中心,且 单节点-》关闭 register-with-eureka 和 fetch-registry
(2)集群
和单机的区别:要将register-with-eureka和fetch-registry设置为true或者直接删掉
service-url要写入的是其他注册中心的地址,多个用逗号分隔
作为同一个集群,spring.application.name要一致
server1:
server:
port: 8761
spring:
application:
name: eureka-server
eureka:
instance:
hostname: eureka01 #这个主机名是指eureka注册中心的主机地址嘛?
prefer-ip-address: true #启用ip注册,到时候那个信息显示的就不是电脑地址,是ip地址
instance-id: ${spring.cloud.client.ip-address}:${server.port}
client:
service-url:
#暴露注册地址
#相互注册
defaultZone: http://localhost:8762/eureka/
server2:
server:
port: 8762
spring:
application:
name: eureka-server
eureka:
instance:
hostname: eureka01 #这个主机名是指eureka注册中心的主机地址嘛?
prefer-ip-address: true #启用ip注册,到时候那个信息显示的就不是电脑地址,是ip地址
instance-id: ${spring.cloud.client.ip-address}:${server.port}
client:
service-url:
#暴露注册地址
#相互注册
defaultZone: http://localhost:8761/eureka/
(3)启动类
要用@EnableEurekaServer标志为注册中心开启
@SpringBootApplication
@EnableEurekaServer
public class EurekaServerApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaServerApplication.class,args);
}
}
三、提供者
(1)单机
client.service-url用逗号将所有注册中心写入
server:
port: 7071
spring:
application:
name: sevice-product
eureka:
instance:
prefer-ip-address: true #启用ip注册,到时候那个信息显示的就不是电脑地址,是ip地址
instance-id: ${spring.cloud.client.ip-address}:${server.port}
client:
service-url:
#注册集群相互注册,提供者/消费者是都注册到中心里
defaultZone: http://localhost:8761/eureka/,http://localhost:8762/eureka/
(2)集群
server1和上面一样
server2只需要修改server.port,和server1不同即可
(3)启动类
因为yml文件已经配置好了eureka,所以可以不用写@EnableEurekaClient
@SpringBootApplication
//开启eureka client注解,如果版本已经配置了注册中心,就会默认开启该注解
//约定>配置
//@EnableEurekaClient
public class ServiceProviderApplication {
public static void main(String[] args) {
SpringApplication.run(ServiceProviderApplication.class,args);
}
}
(4)pojo,service,controller
a. pojo
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Product implements Serializable {
private Integer id;
private String productName;
private Double productPrize;
private Integer productNum;
}
b.service
public interface ProductService {
/**
* 查询商品列表
* @return
*/
List<Product> selectProductList();
}
@Service
public class ProductServiceImpl implements ProductService{
/**
* 查询商品列表
* @return
*/
@Override
public List<Product> selectProductList() {
return Arrays.asList(
new Product(1,"手机",4000D,5),
new Product(2,"电脑",8000D,7),
new Product(3,"耳机",400.92D,100)
);
}
}
c.controller
@RestController
@RequestMapping("/product")
public class ProductController {
@Autowired
private ProductService productService;
@GetMapping("/list")
public List<Product> selectProductList(){
return productService.selectProductList();
}
}
四、消费者
(1)yml
register-with-eureka和 registry-fetch-interval-seconds :当这个消费者只有消费者的身份,不是同时担任提供者,就可以不注册到注册中心,每间隔一定的时间去服务器拉信息
server:
port: 9090
spring:
application:
name: sevice-consumer
#只是消费的服务,本身不提供服务
eureka:
client:
service-url:
#注册集群相互注册,提供者/消费者是都注册到中心里
defaultZone: http://localhost:8761/eureka/,http://localhost:8762/eureka/
register-with-eureka: false #不注册到注册中心
registry-fetch-interval-seconds: 10 #client间隔10s去服务器拉信息
(2)启动类
消费者的启动类不需要加@注解
(3)pojo、service、controller
a.pojo
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Order implements Serializable {
private Integer id;
private String orderNo;
private String orderAddress;
private Double totalPrize;
private List<Product> productList;
}
b.service
public interface ProductService {
/**
* 查询商品列表
* @return
*/
List<Product> selectProductList();
}
消费服务:三种形式:1、DiscoveryClient 2、LoadBalancerClient 3、@LoadBalanced
2和3和负载均衡ribbon有关
1、DiscoveryClient
请求【商品微服务】时,需要访问“提供者”,要在注册中心寻找【商品微服务】,得到主机名端口号作URL的拼接,再去请求获得结果
用discoveryClient.getServices()获取服务列表
用discoveryClient.getInstances(”服务名“)获取”服务名“对应服务
用s1.getHost()和s1.getPort()获取服务的端口号和主机名,再拼接请求的Url
用restTemplate.exchange(请求url,get/post/delete,请求参数,返回的结果类型转换),得到请求结果
注意:其中restTemplate,需要在启动类中加入
@Bean
public RestTemplate restTemplate(){
return new RestTemplate();
}
-------------------------------------
@Service
public class OrderServiceImpl implements OrderService{
@Autowired
private RestTemplate restTemplate;
/**
* 源数据对象,发现注册中心的服务列表,根据名称选择服务,
*/
@Autowired
private DiscoveryClient discoveryClient;
@Autowired
private LoadBalancerClient loadBalancerClient; //负载均衡ribbon
@Override
public Order selectOrderById(Integer id) {
return new Order(id,"order-001","中国",31998D,selectProductListByDiscoveryClient());
}
private List<Product> selectProductListByDiscoveryClient(){
StringBuffer sb=null;
//获取服务列表
List<String> serviceIds = discoveryClient.getServices();
if (CollectionUtils.isEmpty(serviceIds)) {
return null;
}
List<ServiceInstance> instances = discoveryClient.getInstances("sevice-product");
if (CollectionUtils.isEmpty(instances)){
return null;
}
ServiceInstance s1 = instances.get(0);
sb=new StringBuffer();
sb.append("http://"+s1.getHost()+":"+s1.getPort()+"/product/list");
ResponseEntity<List<Product>> response = restTemplate.exchange(
sb.toString(),
HttpMethod.GET,
null,
new ParameterizedTypeReference<List<Product>>() {
}
);
return response.getBody();
}
2、LoadBalancerClient
中间请求【商品微服务】的代码有变化,不需要再通过获取服务列表-》获取具体服务
直接用loadBalancerClient.choose("sevice-product");得到具体服务
注意:要@Autowired private LoadBalancerClient loadBalancerClient; //负载均衡ribbon
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
-------------------------------------
private List<Product> selectProductListByDiscoveryClient(){
StringBuffer sb=null;
//获取服务列表
ServiceInstance si = loadBalancerClient.choose("sevice-product");
if (si==null){
return null;
}
sb=new StringBuffer();
sb.append("http://"+si.getHost()+":"+si.getPort()+"/product/list");
System.out.println(sb.toString());
ResponseEntity<List<Product>> response = restTemplate.exchange(
sb.toString(),
HttpMethod.GET,
null,
new ParameterizedTypeReference<List<Product>>() {
}
);
return response.getBody();
}
3、@LoadBalanced
直接使用restTemplate.exchange()即可,但是里面的URL的拼接是由 微服务名 代替了主机名端口号http://sevice-product/product/list
注:在启动类的RestTemplate中要添加
@Bean
@LoadBalanced //负载均衡注解
public RestTemplate restTemplate(){
return new RestTemplate();
}
private List<Product> selectProductListByDiscoveryClient(){
ResponseEntity<List<Product>> response = restTemplate.exchange(
//地址是由服务项目的名称写下
"http://sevice-product/product/list",
HttpMethod.GET,
null,
new ParameterizedTypeReference<List<Product>>() {
}
);
return response.getBody();
}
五、Eureka自我保护机制
通过renew发送心跳,看eureka是否可用,每30s一个心跳包,若90s没有收到回复,服务会被删除停服的原因:1、自己把服务关闭 2、网络问题
触发自我保护机制条件:15分钟失败比例低于85%,就不会过期,提示警告
直接停服:无论什么原因,只要服务挂掉,就把服务从注册中心移除【默认是自我保护开启】
【把自我保护关闭】在注册中心的yml文件修改:
eureka: server: enable-self-preservation: false #false不开启自我保护,true开启自我保护 eviction-interval-timer-in-ms: 60000 #清理间隔:毫秒,60s就会清理一次
优雅停服:
自己停掉的服务,会从注册中心移除;但因网络原因挂掉的服务,注册中心不会移除
需要在提供者的pom文件中添加acuator依赖:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency>
在提供者服务的yml文件修改:
#度量指标监控与健康检查 management: endpoints: web: exposure: include: shutdown #开启shutdown端点访问 ‘*‘所有端点(除shutdown外) endpoint: shutdown: enabled: true #优雅停服
六、注册中心与Security
在注册中心的pom文件中添加依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
在yml文件中添加访问注册中心时的用户名、密码
spring:
security:
user:
name: root
password: 123456
然后在注册中心、消费者、提供者的yml文件,都要把defaultZone作修改
defaultZone: http://root:123456@localhost:8762/eureka/
改为http://用户名:密码@主机名:端口号/eureka
注册中心添加security配置类
解决多个注册中心跨域注册问题,方法:
csrf防御机制 get post delete都是有风险的,没有csrf token会拦截
(1)忽略/eureka/**的请求
(2)保持密码验证,同时禁用csrf
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
super.configure(http);
http.csrf().ignoringAntMatchers("/eureka/**");
}
}