一、首先创建一个用以提供服务的Springcloud客户端service-user.
1. 依赖
implementation 'org.springframework.cloud:spring-cloud-starter-consul-discovery'
implementation 'org.springframework.boot:spring-boot-starter-actuator'
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
其中'org.springframework.boot:spring-boot-starter-actuator'是必要的,若无则会造成consul服务健康检查失败。
同时需要说明一点,由于我们打算将服务提供者多实例部署,为方便起见,我们需要将程序打包,所以我们要配置一下打包插件。
jar {
baseName = 'service-user'
version = '1.0.0'
manifest {
attributes "Manifest-Version": 1.0,
'Main-Class': 'com.chris.user.UserApplication'
}
}
2.配置文件application.yml
server:
port: 8001
spring:
application:
name: service-user
cloud:
consul:
host: localhost
port: 8500
discovery:
enabled: true
register: true
instance-id: ${spring.application.name}-${instance-id-suffix}
service-name: ${spring.application.name}
prefer-ip-address: true
ip-address: 192.168.0.100
instance-id-suffix: 01
简单清晰,我们在本地启动了一个consul,所以host是localhost,由于我们要测试负载均衡,准备启动多个服务提供者实例,所以在实例后面加不同的后缀用以区分。
3.启动类。我们的启动类就是根据向导自动生成的,没有添加任何东西。
package com.chris.user;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class UserApplication {
public static void main(String[] args) {
SpringApplication.run(UserApplication.class, args);
}
}
4. 数据模型UserModel,需要实现序列化接口。
package com.chris.user.model;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
/**
* @author Chris Chan
* Create on 2021/4/22 13:17
* Use for:
* Explain:
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class UserModel implements Serializable {
private String username;
private int age;
private String address;
private String job;
}
5. 一个通用的接口,用以在Controller和Service同步实现,甚至在服务消费者那里也需要复制使用。
补充一句,数据模型和服务接口很重要,这是RPC进行产销对接很重要的两个类,像Dubbo不仅要求数据结构相同,连包名都要相同,而我们这里仅仅要求数据结构相同,方便数据传输前后的编解码。建议合适的方式是单独封装为通用的库,给服务产销双方引用。
package com.chris.user.common;
import com.chris.user.model.UserModel;
import java.util.List;
/**
* @author Chris Chan
* Create on 2021/4/22 13:19
* Use for:
* Explain:
*/
public interface UserApi {
List<UserModel> findAll();
UserModel findById(int id);
String port();
}
我这里设计三个方法是有用意的:
fandAll()方法测试无参数RPC调用;
findById()测试有参数RPC调用;
port()方法则是用来测试负载均衡的。多实例部署在本地,要求端口不同,我们根据端口号可以看出负载均衡的效果。
6. service继承和实现
package com.chris.user.service;
import com.chris.user.common.UserApi;
/**
* @author Chris Chan
* Create on 2021/4/22 13:19
* Use for:
* Explain:
*/
public interface UserService extends UserApi {
}
package com.chris.user.service;
import com.chris.user.model.UserModel;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import java.util.Arrays;
import java.util.List;
/**
* @author Chris Chan
* Create on 2021/4/22 13:20
* Use for:
* Explain:
*/
@Service
public class UserServiceImpl implements UserService {
@Value("${server.port}")
int port;
@Override
public List<UserModel> findAll() {
return Arrays.asList(
new UserModel("Chris", 42, "上海", "Java开发"),
new UserModel("Mike", 24, "北京", "销售"),
new UserModel("Rose", 26, "西安", "HR"),
new UserModel("John", 36, "广州", "总经理助理"),
new UserModel("Marry", 42, "深圳", "运营")
);
}
@Override
public UserModel findById(int id) {
return new UserModel("Billbo", 100, "夏尔", "魔戒持有者");
}
@Override
public String port() {
return "Port: " + port;
}
}
先继承后实现不是必要的,只是我们假设通用接口UserApi是底层提供给我们的,我们可能要对通用接口进行扩展,所以先继承。
7. controller实现。也可先继承后实现。
package com.chris.user.web;
import com.chris.user.common.UserApi;
import com.chris.user.model.UserModel;
import com.chris.user.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
/**
* @author Chris Chan
* Create on 2021/4/22 13:27
* Use for:
* Explain:
*/
@RestController
@RequestMapping("api/user")
public class UserController implements UserApi {
@Autowired
UserService userService;
@GetMapping("list")
@Override
public List<UserModel> findAll() {
return userService.findAll();
}
@GetMapping("get")
@Override
public UserModel findById(int id) {
return userService.findById(id);
}
@GetMapping("port")
@Override
public String port() {
return userService.port();
}
}
看起来,controller像是给service做了一个代理。这只是一个简单的示例。
二、创建一个服务消费者端service-worker。
依赖跟服务提供者一样。我们还需要把在服务提供者工程创建好的数据模型UserModel和通用接口UserApi复制过来。
1. 配置文件application.yml。配置文件跟服务提供者也是一样的,只需要修改一下spring.application.name这个参数就可以了。
server:
port: 8011
spring:
application:
name: service-worker
cloud:
consul:
host: localhost
port: 8500
discovery:
enabled: true
register: true
instance-id: ${spring.application.name}-${instance-id-suffix}
service-name: ${spring.application.name}
prefer-ip-address: true
ip-address: 192.168.0.100
instance-id-suffix: 01
2. 启动类。
在这里就要说明一下。我们实现服务消费者的RPC远程调用有两种思路,一种是通过RestTemplate来发出请求,一种是通过FeignClient来实现。这里我们先实现第一种。所以我们需要先创建RestTemplate的Bean,我们打算在启动类创建,当然也可以在别处创建。
package com.chris.worker;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;
@SpringBootApplication
public class WorkerApplication {
public static void main(String[] args) {
SpringApplication.run(WorkerApplication.class, args);
}
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
@LoadBalanced用来实现负载均衡。
3. service实现类。
package com.chris.worker;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import java.util.List;
/**
* @author Chris Chan
* Create on 2021/4/21 16:00
* Use for:
* Explain:
*/
@Service
public class UserService implements UserApi {
@Autowired
RestTemplate restTemplate;
@Override
public List<UserModel> findAll() {
ResponseEntity<List<UserModel>> responseEntity = restTemplate.exchange(
"http://service-user/api/user/list",
HttpMethod.GET,
null,
new ParameterizedTypeReference<List<UserModel>>() {
});
return responseEntity.getBody();
}
@Override
public UserModel findById(int id) {
ResponseEntity<UserModel> responseEntity = restTemplate.exchange(
"http://service-user/api/user/get?id=" + id,
HttpMethod.GET,
null,
new ParameterizedTypeReference<UserModel>() {
});
return responseEntity.getBody();
}
@Override
public String port() {
ResponseEntity<String> responseEntity = restTemplate.exchange(
"http://service-user/api/user/port",
HttpMethod.GET,
null,
new ParameterizedTypeReference<String>() {
});
return responseEntity.getBody();
}
}
4. 控制层实现类。
package com.chris.worker.web;
import com.chris.worker.common.UserApi;
import com.chris.worker.model.UserModel;
import com.chris.worker.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
/**
* @author Chris Chan
* Create on 2021/4/22 13:27
* Use for:
* Explain:
*/
@RestController
@RequestMapping("api/user")
public class UserController implements UserApi {
@Autowired
UserService userService;
@GetMapping("list")
@Override
public List<UserModel> findAll() {
return userService.findAll();
}
@GetMapping("get")
@Override
public UserModel findById(int id) {
return userService.findById(id);
}
@GetMapping("port")
@Override
public String port() {
return userService.port();
}
}
至此我们的工程都完成了。
下面说一下使用FeignClient来实现的区别。
区别一,是需要添加对FeignClient的依赖
implementation 'org.springframework.cloud:spring-cloud-starter-openfeign'
区别二,不需要创建TestTemplate的Bean。
区别三,就是service,我们不需要去实现UserApi,继承后覆写所有方法,然后加上几个注解就好了。
package com.chris.worker.service;
import com.chris.worker.common.UserApi;
import com.chris.worker.model.UserModel;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import java.util.List;
/**
* @author Chris Chan
* Create on 2021/4/22 13:19
* Use for:
* Explain:
*/
@FeignClient("service-user")
public interface UserService extends UserApi {
@GetMapping("/api/user/list")
@Override
List<UserModel> findAll();
@GetMapping("/api/user/get")
@Override
UserModel findById(@RequestParam("id") int id);
@GetMapping("/api/user/port")
@Override
String port();
}
类上的注解指明服务提供者的服务名,方法注解则明确了请求方式、接口路径,参数表要加@RequestParam()注解。
控制层不用变。
三、测试
1. 本地运行consul一个开发模式的服务器
consul agent -dev
查看页面
http://localhost:8500/
2. 服务提供者端打包
gradle build
完成之后,在build/libs/下可以看到我们打好的包。
3. 我们打开多个终端,通过命令,运行多个服务提供者。
java -jar build/libs/consul-user-demo-20210422-0.0.1-SNAPSHOT.jar --server.port=8001 --instance-id-suffix=01
java -jar build/libs/consul-user-demo-20210422-0.0.1-SNAPSHOT.jar --server.port=8002 --instance-id-suffix=02
java -jar build/libs/consul-user-demo-20210422-0.0.1-SNAPSHOT.jar --server.port=8003 --instance-id-suffix=03
我们在运行时修改了服务端口号和实例ID的后缀。看看页面效果。consul进行玩健康检查后,把所有的实例都展示出来。
4. 我们在运行服务消费者端,我们只运行一个,直接IDE启动就好了。
5. 访问服务消费者端
http://localhost:8011/api/user/list
http://localhost:8011/api/user/get?id=1
6.测试负载均衡
http://localhost:8011/api/user/port
不断刷新,会看到端口号不断在发生变化。