springcloud实战

微服务通常是一种架构模式,提倡将单一服务拆分成一组小的服务,每个服务运行在自己独立的进程中,服务之间采用轻量级的通信机制互相沟通,基于HTTP的RESTFUL API。

springboot专注于快速方便的开发单个个体微服务。

springcloud是关注全局的微服务协调整理框架,将springboot开发的一个个单体微服务整合并管理起来。为各个微服务之间提供配置管理,服务发现,断路器,路由等集成服务。

springboot可以离开springcloud独立开发项目,但springcloud是依赖springboot。

Dubbo的定位更侧重于是一款RPC框架,而springcloud的目标是微服务框架的一站式解决方案。

Dubbo springcloud
服务注册中心 Zookeeper Eureka
服务调用方式 RPC Rest API
服务监控 Dubbo-monitor admin
断路器 不完善 Hystrix

*实体类 *

针对于ORM 的映射,即类表关系映射。

其中将实体类都放到api文件夹中,这样服务提供者和服务消费者就不需要再自己的模块下创建文件夹。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import java.io.Serializable;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.experimental.Accessors;


@NoArgsConstructor
//@AllArgsConstructor
@Data
//链式调用初始化
@Accessors(chain=true)
public class Dept implements Serializable// 必须要序列化接口
{
private Long deptno; // 主键
private String dname; // 部门名称
private String db_source;// 来自具体的数据库,因为微服务架构可以一个服务对应一个数据库,同一个信息被存储到不同数据库
public Dept(String dname)
{
super();
this.dname = dname;
}
}

mvn clean install后给其他模块引用,达到通用目的。当需要用到数据库表对应的实体类时,不用每个工程都定义一份,直接引用本模块即可。

约定 -> 配置 ->编码,先把软性的配置环境搭建好,再进行编码实践。

编写服务提供者模块

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

<dependency>
<groupId>com.atguigu.springcloudgroupId>
<artifactId>microservicecloud-apiartifactId>
<version>${project.version}version>
dependency>

<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>springloadedartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-devtoolsartifactId>
dependency>

修改配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
server:
port: 8001

mybatis:
config-location: classpath:mybatis/mybatis.cfg.xml # mybatis配置文件所在路径
type-aliases-package: com.xxx.entities # 所有Entity别名类所在包
mapper-locations:
- classpath:mybatis/mapper/**/*.xml # mapper映射文件

spring:
application:
name: microservicecloud-dept
datasource:
type: com.alibaba.druid.pool.DruidDataSource # 当前数据源操作类型
driver-class-name: org.gjt.mm.mysql.Driver # mysql驱动包
url: jdbc:mysql://localhost:3306/cloudDB01 # 数据库名称
username: root
password: admin
dbcp2:
min-idle: 5 # 数据库连接池的最小维持连接数
initial-size: 5 # 初始化连接数
max-total: 5 # 最大连接数
max-wait-millis: 200 # 等待连接获取的最大超时时间

eureka:
client: #客户端注册进eureka服务列表内
service-url:
#defaultZone: http://localhost:7001/eureka
defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/
instance:
instance-id: microservicecloud-dept8001
prefer-ip-address: true #访问路径可以显示IP地址

info:
app.name: xxx
company.name: xxx
build.artifactId: $project.artifactId$
build.version: $project.version$

配置mybatis的xml文件

1
2
3
4
5
6
7
8
9
10


PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">

<configuration>
<settings>
<setting name="cacheEnabled" value="true" />
settings>

configuration>

编写dao接口

1
2
3
4
5
6
7
8
9
10
11
import java.util.List;
import org.apache.ibatis.annotations.Mapper;
import com.atguigu.springcloud.entities.Dept;

@Mapper
public interface DeptDao
{
public boolean addDept(Dept dept);
public Dept findById(Long id);
public List findAll();
}

配置mybatis下mapper中的xml文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17


"http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.atguigu.springcloud.dao.DeptDao">

<select id="findById" resultType="Dept" parameterType="Long">
select deptno,dname,db_source from dept where deptno=#{deptno};
select>

<select id="findAll" resultType="Dept">
select deptno,dname,db_source from dept;
select>
<insert id="addDept" parameterType="Dept">
INSERT INTO dept(dname,db_source) VALUES(#{dname},DATABASE());
insert>

mapper>

编写服务对应的接口

1
2
3
4
5
6
7
8
import java.util.List;

public interface DeptService
{
public boolean add(Dept dept);
public Dept get(Long id);
public List list();
}

编写服务实现类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@Service
public class DeptServiceImpl implements DeptService
{
@Autowired
//Dao接口的注入
private DeptDao dao;

@Override
public boolean add(Dept dept)
{
return dao.addDept(dept);
}
@Override
public Dept get(Long id)
{
return dao.findById(id);
}
@Override
public List list()
{
return dao.findAll();
}

}

编写微服务子模块对外接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
//前后端分离的架构,返回给前端字符串,之前springMVC使用Controller
@RestController
public class DeptController
{
@Autowired
//注入服务层
private DeptService service;
//服务发现
@Autowired
private DiscoveryClient client;

@RequestMapping(value = "/dept/add", method = RequestMethod.POST)
public boolean add(@RequestBody Dept dept)
{
return service.add(dept);
}

@RequestMapping(value = "/dept/get/{id}", method = RequestMethod.GET)
public Dept get(@PathVariable("id") Long id)
{
return service.get(id);
}

@RequestMapping(value = "/dept/list", method = RequestMethod.GET)
public List list()
{
return service.list();
}


@RequestMapping(value = "/dept/discovery", method = RequestMethod.GET)
public Object discovery()
{
//盘点拥有的微服务列表
List list = client.getServices();
System.out.println("**********" + list);

List srvList = client.getInstances("MICROSERVICECLOUD-DEPT");
for (ServiceInstance element : srvList) {
System.out.println(element.getServiceId() + "\t" + element.getHost() + "\t" + element.getPort() + "\t"
+ element.getUri());
}
return this.client;
}

}

编写消费者模块

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;

import com.netflix.loadbalancer.IRule;
import com.netflix.loadbalancer.RetryRule;

@Configuration
//相当于spring中的applicationContext.xml
//spring 中的写法
//@Bean
/**public UserServcie getUserServcie()
*{
* return new UserServcieImpl();
*}
*/
//applicationContext.xml == ConfigBean(@Configuration)
//
public class ConfigBean
{
@Bean
@LoadBalanced//Spring Cloud Ribbon是基于Netflix Ribbon实现的一套客户端 负载均衡的工具。
public RestTemplate getRestTemplate()
{
return new RestTemplate();
}

@Bean
public IRule myRule()
{
//return new RoundRobinRule();
//return new RandomRule();//达到的目的,用我们重新选择的随机算法替代默认的轮询。
return new RetryRule();
}
}

消费者controller层

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
//见到controller加上rest
@RestController
//传统的controller层需要注入service层
public class DeptController_Consumer
{

private static final String REST_URL_PREFIX = "http://localhost:8001";


/**
*RestTemplate提供了多种便捷访问远程http服务的方法,是一种简单便捷的访问restful服务模板类,是spring 提供的用于访问rest服务的客户端模板工具类。
* 使用 使用restTemplate访问restful接口非常的简单粗暴无脑。
*(url, requestMap, ResponseBean.class)这三个参数分别代表 REST请求地址、请求参数、HTTP响应转换被转换成的对象类型。
*/
@Autowired
private RestTemplate restTemplate;

@RequestMapping(value = "/consumer/dept/add")
public boolean add(Dept dept)
{
return restTemplate.postForObject(REST_URL_PREFIX + "/dept/add", dept, Boolean.class);
}

@RequestMapping(value = "/consumer/dept/get/{id}")
public Dept get(@PathVariable("id") Long id)
{
return restTemplate.getForObject(REST_URL_PREFIX + "/dept/get/" + id, Dept.class);
}

@SuppressWarnings("unchecked")
@RequestMapping(value = "/consumer/dept/list")
public List list()
{
return restTemplate.getForObject(REST_URL_PREFIX + "/dept/list", List.class);
}

// 测试@EnableDiscoveryClient,消费端可以调用服务发现
@RequestMapping(value = "/consumer/dept/discovery")
public Object discovery()
{
return restTemplate.getForObject(REST_URL_PREFIX + "/dept/discovery", Object.class);
}

}

编写Eureka模块

provider注册到Eureka (provider-eureka)

相比较于Eureka的xml文件

1
2
3
4
5
6
7
8
9

<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-eurekaartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-configartifactId>
dependency>
1
2
3
4
5

<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-eureka-serverartifactId>
dependency>

相比较于Eureka的application.yml文件

1
2
3
4
5
6
7
8
9
10
# eureka-client客户端
eureka:
client: #客户端注册进eureka服务列表内
service-url:
#defaultZone: http://localhost:7001/eureka
defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/
instance:
#修改主机映射名称
instance-id: microservicecloud-dept8001
prefer-ip-address: true #访问路径可以显示IP地址
1
2
3
4
5
6
7
8
9
10
11
12
13
# eureka-server服务端
server:
port: 7001

eureka:
instance:
hostname: eureka7001.com #eureka服务端的实例名称
client:
register-with-eureka: false #false表示不向注册中心注册自己。
fetch-registry: false #false表示自己端就是注册中心,我的职责就是维护服务实例,并不需要去检索服务
service-url:
#单机 defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/ #设置与Eureka Server交互的地址查询服务和注册服务都需要依赖这个地址(单机)。
defaultZone: http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/

客户端启动类

也证明了Eureka是C / S结构。

1
2
3
4
5
6
7
8
9
10
11
@SpringBootApplication
@EnableEurekaClient //本服务启动后会自动注册进eureka服务中
//服务端是EnableEurekaServer
@EnableDiscoveryClient //服务发现
public class DeptProvider8001_App
{
public static void main(String[] args)
{
SpringApplication.run(DeptProvider8001_App.class, args);
}
}

info内容构建

修改当前的pom文件

1
2
3
4
5

<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-actuatorartifactId>
dependency>

父工程的pom文件需要添加一个build模块

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<build>
<finalName>microservicecloudfinalName>
<resources>
<resource>

<directory>src/main/resourcesdirectory>
<filtering>truefiltering>
resource>
resources>
<plugins>
<plugin>
<groupId>org.apache.maven.pluginsgroupId>
<artifactId>maven-resources-pluginartifactId>
<configuration>
<delimiters>
<delimit>$delimit>
delimiters>
configuration>
plugin>
plugins>
build>

修改模块下的yaml文件

1
2
3
4
5
info: 
app.name: xxx
company.name: xxx
build.artifactId: $project.artifactId$
build.version: $project.version$

Euraka的自我保护机制

某时刻某一个微服务不可用了,则Eureka不会立即清理,依旧会对该微服务的信息进行保存。

默认情况下,如果Eureka Server在一定时间内没有接收到某个微服务实例的心跳,Eureka Server将会注销该实例(默认90 s)。当网络分区发生故障时,微服务与Eureka Server之间无法保证正常的通信,以上行为可能变得非常危险了。

当Eureka Server短时间内丢失多个客户端的话,那么这个节点就会进入自我保护模式,一旦进入该模式,Eureka Server就会保护注册表中的信息,不再删除服务注册表中的信息。故障恢复之后,当它收到的心跳数重新恢复到阈值以上时,则Eureka Server节点会自动退出自我保护模式。

可以通过eureka.server.enable-self-preservation = false来禁用自我保护模式

Eureka服务发现

1
2
3
//服务发现
@Autowired
private DiscoveryClient client;

在Eureka 客户端的主启动类添加注解

1
@EnableDiscoveryClient //服务发现

消费者调用服务发现

1
2
3
4
5
@RequestMapping(value = "/consumer/dept/discovery")
public Object discovery()
{
return restTemplate.getForObject(REST_URL_PREFIX + "/dept/discovery", Object.class);
}

Eureka相比于Zookeeper的好处

著名的CAP理论: 一个分布式系统不可能同时满足C(一致性),A(可用性)和P(分区容错性)。由于分区容错性P是必须保证的 ,故只能在A和C之间权衡。

  • zookeeper保证的是CP;当注册中心查询服务列表时,可以容忍的是注册中心返回的是几分钟之前的注册信息,但不能接受服务直接down掉不可用。也就是说服务注册功能对可用性的要求高于一致性,但zk会出现:当master节点因为网络故障与其他节点失去联系,剩余节点会重新进行leader选举,问题在于选举leader的时间太长,一般在30-120s,选举期间zk集群是不可用的,会导致在选举期间整个注册服务处于瘫痪状态
  • Eureka保证的是AP。优先保证可用性,各个节点都是平等的,几个节点挂掉之后不会影响正常节点的工作,剩余的节点依然可以提供注册和查询服务,而Eureka的客户端 在注册时如果发现连接失败,则会自动切换到其他节点,只不过查到的信息可能不是最新的(不保证强一致性),除此之外,Eureka还有一种自我保护机制,如果在15分钟内超过85%的节点都没有正常的心跳,则Eureka认为客户端与注册中心出现了网络故障。
    • Eureka不再从注册列表中国移除因为长时间没收到心跳而应该过期的服务。
    • Eureka仍然能够接受新服务的注册和查询请求,但是不会被同步到其他节点上,即保证当前节点依然可用。
    • 当网络稳定时,当前实例新的注册信息会被同步到其他的节点中。

进行Ribbon的配置

所谓ribbon,主要是提供客户端的软件负载均衡算法,简单的说就是在配置文件中列出Load Balancer后面的机器。

补充消费者模块的配置文件和yaml文件

1
2
3
4
5
eureka:
client:
register-with-eureka: false
service-url:
defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/
1
2
3
4
5
6
7
8
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-ribbonartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-configartifactId>
dependency>

在 ConfigBean 类上添加注解

1
2
3
4
5
6
@Bean
@LoadBalanced//Spring Cloud Ribbon是一套客户端负载均衡的工具。
public RestTemplate getRestTemplate()
{
return new RestTemplate();
}

也就是要求消费者在调用provider模块时使用的restTemplate访问restful接口自带负载均衡。

最终达到的效果是ribbon和eureka整合后consumer可以直接调用服务而不用关心地址和端口号。

0%