微服务springcloud(一)

服务治理SOA

当服务越来越多,容量的评估,小服务资源的浪费等问题逐渐显现,此时需增加一个调度中心基于访问压力实时管理集群容量,提高集群利用率。此时,用于提高机器利用率的资源调度和治理中心(SOA)是关键
在这里插入图片描述以前出现了什么问题?

  • 服务越来越多,需要管理每个服务的地址
  • 调用关系错综复杂,难以理清依赖关系
  • 服务过多,服务状态难以管理,无法根据服务情况动态管理

服务治理要做什么?

  • 服务注册中心,实现服务自动注册和发现,无需人为记录服务地址
  • 服务自动订阅,服务列表自动推送,服务调用透明化,无需关心依赖关系
  • 动态监控服务状态监控报告,人为控制服务状态

缺点:

  • 服务间会有依赖关系,一旦某个环节出错会影响较大
  • 服务关系复杂,运维、测试部署困难,不符合DevOps思想

微服务与SOA

在这里插入图片描述微服务的特点:

  • 单一职责:微服务中每一个服务都对应唯一的业务能力,做到单一职责
  • 微:微服务的服务拆分粒度很小,例如一个用户管理就可以作为一个服务。每个服务虽小,但“五脏俱全”。
  • 面向服务:面向服务是说每个服务都要对外暴露服务接口API。并不关心服务的技术实现,做到与平台和语言无关,也不限定用什么技术实现,只要提供Rest的接口即可。
  • 自治:自治是说服务间互相独立,互不干扰
    • 团队独立:每个服务都是一个独立的开发团队,人数不能过多。
    • 技术独立:因为是面向服务,提供Rest接口,使用什么技术没有别人干涉
    • 前后端分离:采用前后端分离开发,提供统一Rest接口,后端不用再为PC、移动段开发不同接口
    • 数据库分离:每个服务都使用自己的数据源
    • 部署独立,服务间虽然有调用,但要做到服务重启不影响其它服务。有利于持续集成和持续交付。每个服务都是独立的组件,可复用,可替换,降低耦合,易维护

微服务结构图

在这里插入图片描述

RPC与Http调用方式

  • RPC方式更加透明,对用户更方便。Http方式更灵活,没有规定API和语言,跨语言、跨平台;
  • RPC方式需要在API层面进行封装,限制了开发的语言环境。
  • 速度来看,RPC要比http更快,虽然底层都是TCP,但是http协议的信息往往比较臃肿,不过可以采用gzip压缩。
  • 难度来看,RPC实现较为复杂,http相对比较简单
  • 灵活性来看,http更胜一筹,因为它不关心实现细节,跨平台、跨语言。

如何选择:
如果对效率要求更高,并且开发过程使用统一的技术栈,那么用RPC还是不错的。
如果需要更加灵活,跨语言、跨平台,显然http更合适。

Http的三种Client工具:

  • HttpClient
  • OKHttp
  • URLConnection

Spring的RestTemplate

在启动类中注册一个restTemplate

@Bean
public RestTemplate restTemplate() {
    // 默认的RestTemplate,底层是走JDK的URLConnection方式。
	return new RestTemplate();
}

在测试类中进行注入并使用:

@Autowired
private RestTemplate restTemplate;

@Test
public void httpGet() {
	User user = this.restTemplate.getForObject("http://localhost/hello", User.class);
	System.out.println(user);
}

Eureka注册中心

基本架构图

  • Eureka:就是服务注册中心(可以是一个集群),对外暴露自己的地址
  • 提供者:启动后向Eureka注册自己信息(地址,提供什么服务)
  • 消费者:向Eureka订阅服务,Eureka会将对应服务的所有提供者地址列表发送给消费者,并且定期更新
  • 心跳(续约):提供者定期通过http方式向Eureka刷新自己的状态

父类的pom文件

<packaging>pom</packaging>

<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>
 <mapper.starter.version>2.0.3</mapper.starter.version>
 <mysql.version>5.1.32</mysql.version>
</properties>

<!--父亲私有的依赖-->
<dependencyManagement>
    <dependencies>
        <!--springcloud-->
        <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>mybatis-spring-boot-starter</artifactId>
            <version>${mapper.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>

    <!--eureka-client依赖-->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>
</dependencies>

<!--打包插件-->
<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
        </plugin>
    </plugins>
</build>

引入eurekaServer依赖:

<dependency>
		<groupId>org.springframework.cloud</groupId>
		<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
	</dependency>

在启动类中加入注解:

@EnableEurekaServer // 声明这个应用是一个EurekaServer

编写application.yaml配置:

server:
	port: 10086 # 端口
spring:
    application:
    name: eureka-server # 应用名称,会在Eureka中显示
eureka:
    client:
        register-with-eureka: false # 是否注册自己的信息到EurekaServer,默认是true
		fetch-registry: false # 是否拉取其它服务的信息,默认是true
		service-url: # EurekaServer的地址,现在是自己的地址,如果是集群,需要加上其它Server的地址。是一个map结构
  			defaultZone: http://127.0.0.1:${server.port}/eureka

eureka客户端:

引入pom文件:

	<!-- Eureka客户端 -->
	<dependency>
		<groupId>org.springframework.cloud</groupId>
		<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

2、在消费者方启动类上加入注解

@EnableDiscoveryClient // 开启EurekaClient功能

3、编写配置

server:
	port: 8081
spring:
	datasource:
		url: jdbc:mysql://localhost:3306/mydb01
		username: root
		password: 123
	application:
	 	name: user-service # 应用名称
mybatis:
	type-aliases-package: com.leyou.userservice.pojo
eureka:
	 client:
		service-url: # EurekaServer地址
  			defaultZone: http://127.0.0.1:10086/eureka
	instance:
		prefer-ip-address: true # 当调用getHostname获取实例的hostname时,返回ip而不是host名称
		ip-address: 127.0.0.1 # 指定自己的ip信息,不指定的话会自己寻找
  • 这里我们添加了spring.application.name属性来指定应用名称,将来会作为应用的id使用。

消费者端从eurekaServer中获取服务

引入pom依赖:

<!-- Eureka客户端 -->
<dependency>
	<groupId>org.springframework.cloud</groupId>
	<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

启动类上加入注解,开启eureka客户端:

	@EnableDiscoveryClient // 开启EurekaClient功能
 public class UserConsumerDemoApplication {
    @Bean
    public RestTemplate restTemplate() {
        return new RestTemplate(new OkHttp3ClientHttpRequestFactory());
    }
    public static void main(String[] args) {
        SpringApplication.run(UserConsumerDemoApplication.class, args);
    }
}

修改yaml配置:

server:
	port: 8080
spring:
	application:
		name: consumer # 应用名称
eureka:
	client:
		service-url: # EurekaServer地址
  			defaultZone: http://127.0.0.1:10086/eureka
	instance:
		prefer-ip-address: true # 当其它服务获取地址时提供ip而不是hostname
	 	ip-address: 127.0.0.1 # 指定自己的ip信息,不指定的话会自己寻找
	 	instance-id: ${spring.application.name}:${server.port}  #将服务器上的服务id指定为本名称

修改代码:

@Service
public class UserService {

    @Autowired
    private RestTemplate restTemplate;

    @Autowired
    private DiscoveryClient discoveryClient;// Eureka客户端,可以获取到服务实例信息

    public List<User> queryUserByIds(List<Long> ids) {
        List<User> users = new ArrayList<>();
        // String baseUrl = "http://localhost:8081/user/";
        // 根据服务名称,获取服务实例
        List<ServiceInstance> instances = discoveryClient.getInstances("user-service");
        // 因为只有一个UserService,因此我们直接get(0)获取
        ServiceInstance instance = instances.get(0);
        // 获取ip和端口信息
        String baseUrl = "http://"+instance.getHost() + ":" + instance.getPort()+"/user/";
        ids.forEach(id -> {
            // 我们测试多次查询,
            users.add(this.restTemplate.getForObject(baseUrl + id, User.class));
            // 每次间隔500毫秒
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        return users;
    }
   }

Eureka集群配置

先将服务器端的配置进行copy

服务器端的配置:

server:
	port: 10086 # 端口
spring:
	application:
		name: eureka-server # 应用名称,会在Eureka中显示
eureka:
	client:
		service-url: # 配置其他Eureka服务的地址,而不是自己,比如10087
  			defaultZone: http://127.0.0.1:10087/eureka
  • 复制的配置将端口改为10087,地址的端口改为10086,然后启动;

消费端的配置:

eureka:
	client:
		service-url: # EurekaServer地址,多个地址以','隔开
  			defaultZone: http://127.0.0.1:10086/eureka,http://127.0.0.1:10087/eureka

服务提供方的服务预约与服务失效

  • lease-renewal-interval-in-seconds:服务续约(renew)的间隔,默认为30秒

  • lease-expiration-duration-in-seconds:服务失效时间,默认值90秒

      eureka:
      	instance:
      		lease-expiration-duration-in-seconds: 10 #10秒即过期,默认为90秒过期
      		lease-renewal-interval-in-seconds: 5  # 5秒一次心跳,默认为30秒一次心跳
      	client:
      		register-with-erueka:true # 是否将自己的信息注册到eurekaServer中
    

服务消费者的拉取服务

eureka:
	client:
		registry-fetch-interval-seconds: 5 #默认为30秒拉取一次数据
		fetch-registry: true # 是否拉取eurekaServer上的服务 ,如果为true,则会从Eureka Server服务的列表只读备份,然后缓存在本地。

失效剔除和自我保护

有些时候,我们的服务提供方并不一定会正常下线,可能因为内存溢出、网络故障等原因导致服务无法正常工作。Eureka Server需要将这样的服务剔除出服务列表。因此它会开启一个定时任务,每隔60秒对所有失效的服务(超过90秒未响应)进行剔除。

可以通过eureka.server.eviction-interval-timer-in-ms参数对其进行修改,单位是毫秒,生成环境不要修改。

这个会对我们开发带来极大的不变,你对服务重启,隔了60秒Eureka才反应过来。开发阶段可以适当调整,比如10S
自我保护

我们关停一个服务,就会在Eureka面板看到一条警告:
在这里插入图片描述
这是触发了Eureka的自我保护机制。当一个服务未按时进行心跳续约时,Eureka会统计最近15分钟心跳失败的服务实例的比例是否超过了85%。在生产环境下,因为网络延迟等原因,心跳失败实例的比例很有可能超标,但是此时就把服务剔除列表并不妥当,因为服务可能没有宕机。Eureka就会把当前实例的注册信息保护起来,不予剔除。生产环境下这很有效,保证了大多数服务依然可用。

但是这给我们的开发带来了麻烦, 因此开发阶段我们都会关闭自我保护模式:

eureka:
	server:
		enable-self-preservation: false # 关闭自我保护模式(缺省为打开)
		eviction-interval-timer-in-ms: 1000 # 扫描失效服务的间隔时间(缺省为60*1000ms)

负载均衡Robbin

  • 依赖:

      	<dependency>
                  <groupId>org.springframework.cloud</groupId>
                  <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
        </dependency>
    

在RestTemplate的配置方法上添加@LoadBalanced注解:

@Bean
@LoadBalanced
public RestTemplate restTemplate() {
//这里使用的是OKHttp的客户端
return new RestTemplate(new OkHttp3ClientHttpRequestFactory());
}

修改调用方式,不再手动获取ip和端口,而是直接通过服务名称调用:

@Service
public class UserService {

    @Autowired
    private RestTemplate restTemplate;

    @Autowired
    private DiscoveryClient discoveryClient;

    public List<User> queryUserByIds(List<Long> ids) {
        List<User> users = new ArrayList<>();
		/*//通过服务id来获取所有实例
    List<ServiceInstance> instances = discoveryClient.getInstances("user-service");
    //取出第一条实例
    ServiceInstance instance = instances.get(0);
	//获取主机ip:instance.getHost();
	 获取端口号:instance.getPost();
		*/
        // 地址直接写服务名称即可,以前是硬编码,固定的ip和端口
        String baseUrl = "http://user-service/user/";
        ids.forEach(id -> {
            // 我们测试多次查询,
            users.add(this.restTemplate.getForObject(baseUrl + id, User.class));
            // 每次间隔500毫秒
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        return users;
    }
}

创建测试类进行测试:

@RunWith(SpringRunner.class)
@SpringBootTest(classes = UserConsumerDemoApplication.class)
public class LoadBalanceTest {
    @Autowired
    RibbonLoadBalancerClient client;
    @Test
    public void test(){
        for (int i = 0; i < 100; i++) {
            ServiceInstance instance = this.client.choose("user-service");
            System.out.println(instance.getHost() + ":" + instance.getPort());
        }
    }
}

SpringBoot也帮我们提供了修改负载均衡规则的配置入口:

user-service:
	ribbon:
	NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
	--再次测试,变成了随机,默认是轮询

Json转换工具

介绍:HttpClient请求数据后是json字符串,需要我们自己把Json字符串反序列化为对象,我们会使用JacksonJson工具来实现。

JacksonJson是SpringMVC内置的json处理工具,其中有一个ObjectMapper类,可以方便的实现对json的处理:

对象转json

// json处理工具
    private ObjectMapper mapper = new ObjectMapper();
    @Test
    public void testJson() throws JsonProcessingException {
        User user = new User();
        user.setId(8L);
        user.setAge(21);
        user.setName("柳岩");
        user.setUserName("liuyan");
        // 序列化
        String json = mapper.writeValueAsString(user);
        System.out.println("json = " + json);
    }

json转普通对象

// json处理工具
private ObjectMapper mapper = new ObjectMapper();
@Test
public void testJson() throws IOException {
    User user = new User();
    user.setId(8L);
    user.setAge(21);
    user.setName("柳岩");
    user.setUserName("liuyan");
    // 序列化
    String json = mapper.writeValueAsString(user);

    // 反序列化,接收两个参数:json数据,反序列化的目标类字节码
    User result = mapper.readValue(json, User.class);
    System.out.println("result = " + result);
}

json转集合

json转集合比较麻烦,因为你无法同时把集合的class和元素的class同时传递到一个参数。

因此Jackson做了一个类型工厂,用来解决这个问题:

// json处理工具
private ObjectMapper mapper = new ObjectMapper();
@Test
public void testJson() throws IOException {
    User user = new User();
    user.setId(8L);
    user.setAge(21);
    user.setName("柳岩");
    user.setUserName("liuyan");

    // 序列化,得到对象集合的json字符串
    String json = mapper.writeValueAsString(Arrays.asList(user, user));

    // 反序列化,接收两个参数:json数据,反序列化的目标类字节码
    List<User> users = mapper.readValue(json, mapper.getTypeFactory().constructCollectionType(List.class, User.class));
    for (User u : users) {
        System.out.println("u = " + u);
    }
}

json转任意复杂类型

当对象泛型关系复杂时,类型工厂也不好使了。这个时候Jackson提供了TypeReference来接收类型泛型,然后底层通过反射来获取泛型上的具体类型。实现数据转换。

// json处理工具
private ObjectMapper mapper = new ObjectMapper();
@Test
public void testJson() throws IOException {
    User user = new User();
    user.setId(8L);
    user.setAge(21);
    user.setName("柳岩");
    user.setUserName("liuyan");

    // 序列化,得到对象集合的json字符串
    String json = mapper.writeValueAsString(Arrays.asList(user, user));

    // 反序列化,接收两个参数:json数据,反序列化的目标类字节码
    List<User> users = mapper.readValue(json, new TypeReference<List<User>>(){});
    for (User u : users) {
        System.out.println("u = " + u);
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值