spring cloud
Spring Cloud是一系列框架的有序集合。它利用Spring Boot的开发便利性巧妙地简化了分布式系统基础设施的开发,如服务发现注册、配置中心、消息总线、负载均衡、断路器、数据监控等,都可以用Spring Boot的开发风格做到一键启动和部署。Spring将目前各家公司开发的比较成熟、经得起实际考验的服务框架组合起来,通过Spring Boot风格进行再封装屏蔽掉了复杂的配置和实现原理,最终给开发者留出了一套简单易懂、易部署和易维护的分布式系统开发工具包。
配置中心
在分布式系统中,由于服务数量巨多,而每个服务实例都会有一个或几个配置文件(yml,properties,json…)。而这些文件,分布在系统的各个角落,管理起来特别麻烦,为了方便服务配置文件统一管理,实时更新,所以需要分布式配置中心组件。在Spring Cloud中,有分布式配置中心组件springcloud config,它支持配置服务放在配置服务的内存中(即本地),也支持放在远程Git仓库中。在spring cloud config组件中,分两个角色,一是configserver,二是configclient。
在pom.xml引入依赖
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>cn.et</groupId>
<artifactId>ConfigurationCenter</artifactId>
<version>0.0.1-SNAPSHOT</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.10.RELEASE</version>
<relativePath />
</parent>
<dependencies>
<!-- 添加web功能 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 配置中心 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-server</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Edgware.SR2</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
</project>
这里配置文件我们放在GitHub的远程仓库里,远程仓库文件ms-product.properties内容如下
spring.datasource.url=jdbc:mysql://localhost/et_shop
spring.datasource.username=root
spring.datasource.password=LCH
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.application.name=userservice
我们还需要配置端口号和远程仓库地址,创建文件application.properties
server.port=8090
spring.cloud.config.server.git.uri=https://github.com/DreamLCH/conf.git
到此,一个简单的配置中心我们已经配置好了,编写运行类进行测试,新建项目启动类ConfigServer
,通过@EnableConfigServer
开启配置中心服务。
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.config.server.EnableConfigServer;
@SpringBootApplication
@EnableConfigServer
public class ConfigServerMain {
public static void main(String[] args) {
SpringApplication.run(ConfigServerMain.class, args);
}
}
通过访问http://localhost:8090/ms-product.properties可以访问到配置文件的内容
http请求地址和资源文件映射如下(其中application为服务名):
- /{application}/{profile}[/{label}]
- /{application}-{profile}.yml
- /{label}/{application}-{profile}.yml
- /{application}-{profile}.properties
- /{label}/{application}-{profile}.properties
新建一个SpringBoot的客户端项目Application去配置中心获取配置文件的属性值
添加依赖:
<!-- 客户端 微服务 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
添加服务端的配置,在项目的resources目录下,新建bootstrap.yml,在其中添加如下内容:
server:
port: 8888
spring:
application:
name: ms
profiles:
active: product
cloud:
config:
uri: http://localhost:8090
配置启动类,并在Controller中获取Git文件中的属性
新建启动类ApplicationMain,并使用@RestController
注册为Controller接口。使用@Value
获取属性信息:
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@SpringBootApplication
public class ApplicationMain {
@Value("${spring.datasource.url}")
private String url;
@RequestMapping("/url")
public String getUrl() {
return url;
}
public static void main(String[] args) {
SpringApplication.run(ApplicationMain.class, args);
}
}
访问地址:http://localhost:8888/url
显示结果如下:
注册中心
Spring Cloud 封装了 Netflix 公司开发的 Eureka 模块来实现服务注册和发现。Eureka 采用了 C-S 的设计架构。Eureka Server 作为服务注册功能的服务器,它是服务注册中心。而系统中的其他微服务,使用 Eureka 的客户端连接到 Eureka Server,并维持心跳连接。这样系统的维护人员就可以通过 Eureka Server 来监控系统中各个微服务是否正常运行。Spring Cloud 的一些其他模块(比如Zuul)就可以通过 Eureka Server 来发现系统中的其他微服务,并执行相关的逻辑。
Eureka由两个组件组成:Eureka服务器和Eureka客户端。Eureka服务器用作服务注册服务器。Eureka客户端是一个java客户端,用来简化与服务器的交互、作为轮询负载均衡器,并提供服务的故障切换支持。Netflix在其生产环境中使用的是另外的客户端,它提供基于流量、资源利用率以及出错状态的加权负载均衡。
1、Eureka Server
提供服务注册和发现
2、Service Provider
服务提供方
将自身服务注册到Eureka,从而使服务消费方能够找到
3、Service Consumer
服务消费方
从Eureka获取注册服务列表,从而能够消费服务
Eureka Server
spring cloud已经帮我们实现了服务注册中心,我们只需要很简单的几个步骤就可以完成。
添加依赖:
<!-- 注册中心 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka-server</artifactId>
</dependency>
添加配置文件application.yml
#eureka注册中心对外暴露的端口 提供web界面 查看所有注册的微服务
server:
port: 8761
#设置注册中心当前主机的主机名 必须指定appname
eureka:
instance:
hostname: myhost
appname: myhost
client:
#表示是否将自己注册到Eureka Server,默认为true
registerWithEureka: false
#表示是否从Eureka Server获取注册信息,默认为true。
fetchRegistry: false
#该地址是将来 所有微服务用于注册到注册中心的地址 【重要】
serviceUrl:
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
主机的主机名可以去C:\Windows\System32\drivers\etc\hosts文件中设置
127.0.0.1 myhost
127.0.0.1 userservice
添加启动类,加上@EnableEurekaServer注解
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
@SpringBootApplication
@EnableEurekaServer
public class RegServer {
public static void main(String[] args) {
SpringApplication.run(RegServer.class, args);
}
}
启动工程后,访问:http://localhost:8761,可以看到下面的页面,其中还没有发现任何服务
Service Provider
添加依赖pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>cn.et</groupId>
<artifactId>MicroService</artifactId>
<version>0.0.1-SNAPSHOT</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.10.RELEASE</version>
<relativePath /> <!-- lookup parent from repository -->
</parent>
<dependencies>
<!-- 添加web功能 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 客户端 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
<!-- 获取配置中心配置客户端 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
<!-- mysql -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.38</version>
</dependency>
<!-- 集成 mysql -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.2</version>
</dependency>
<!-- 阿里巴巴druid连接池 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.8</version>
</dependency>
<!-- 自动刷新工具 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Edgware.SR2</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
</project>
这里可以看到引入了很多其他的架包,集成了mybatis用于查询数据库数据,使用了druid连接池,数据源datasource我们从之前写好的配置中心去获取。
在resources目录下新建bootstrap.yml配置文件,在其中添加如下内容:
#对外提供接口调用的端口
server:
port: 8888
#设置注册中心当前主机的主机名
eureka:
instance:
hostname: userservice
appname: userservice
#注册到注册中心
client:
#表示是否将自己注册到Eureka Server,默认为true
registerWithEureka: true
#是否将 注册中心上所有的微服务抓取到本地 存储
fetchRegistry: false
#该地址是将来 所有微服务用于注册到注册中心的地址 【重要】
serviceUrl:
defaultZone: http://myhost:8761/eureka/
#去配置中心获取配置
spring:
application:
name: ms
profiles:
active: product
cloud:
config:
uri: http://localhost:8090
创建datasource的bean实例,使用@Value从配置中心获取值
import java.util.ArrayList;
import java.util.List;
import javax.sql.DataSource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.support.http.StatViewServlet;
/**
* <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" >
* </bean>
* @author Administrator
*
*/
@Configuration
@SpringBootApplication
public class MyConfiguration {
@Value("${spring.datasource.url}")
private String url;
@Value("${spring.datasource.username}")
private String userName;
@Value("${spring.datasource.password}")
private String password;
@Value("${spring.datasource.driver-class-name}")
private String driverClassName;
@Bean
public DataSource dataSource() {
DruidDataSource dds = new DruidDataSource();
dds.setDriverClassName(driverClassName);
dds.setUrl(url);
dds.setUsername(userName);
dds.setPassword(password);
return dds;
}
@Bean
public ServletRegistrationBean DruidStatView() {
ServletRegistrationBean sr = new ServletRegistrationBean();
sr.setServlet(new StatViewServlet());
List<String> list = new ArrayList<String>();
list.add("/druid/*");
sr.setUrlMappings(list);
return sr;
}
}
编写dao层接口
import java.util.Map;
import org.apache.ibatis.annotations.Delete;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Update;
import com.et.psm.model.User;
@Mapper
public interface UserDao {
@Select("select id name,pwd password from userlogin where id = #{name}")
public User queryUserByName(@Param("name") String name);
@Insert("insert into userlogin values(#{userName},#{password})")
public void addUser(Map map);
@Update("update userlogin set pwd=#{m.password} where id=#{userName}")
public void updateUser(@Param("userName") String userName, @Param("m") Map map);
@Delete("delete from userlogin where id=#{name}")
public void deleteUser(@Param("name") String name);
}
实体类user
public class User {
private String name;
private String password;
public User() {
super();
// TODO Auto-generated constructor stub
}
public User(String name, String password) {
super();
this.name = name;
this.password = password;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
@Override
public String toString() {
return "User [name=" + name + ", password=" + password + "]";
}
}
service接口实现类,service接口这里就不列出来了
import java.util.HashMap;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.et.psm.dao.UserDao;
import com.et.psm.model.User;
import com.et.psm.service.UserService;
@Service
public class UserServiceImpl implements UserService {
@Autowired
UserDao userdao;
@Override
public Map queryUserByName(String name, String password) {
User user = userdao.queryUserByName(name);
Map map = new HashMap();
if (user == null) {
map.put("statuscode", 0);
map.put("message", "你输入的用户名不存在。");
} else if (user.getPassword().equals(password)) {
map.put("statuscode", 1);
map.put("message", "账号密码正确。");
} else {
map.put("statuscode", -1);
map.put("message", "你输入的密码错误。");
}
return map;
}
@Override
public Map addUser(Map map) {
Map returnMap = new HashMap();
try {
userdao.addUser(map);
returnMap.put("statuscode", 0);
returnMap.put("message", "添加成功。");
} catch (Exception e) {
returnMap.put("statuscode", 1);
returnMap.put("message", "添加失败。");
} finally {
return returnMap;
}
}
@Override
public Map updateUser(String userName, Map map) {
Map returnMap = new HashMap();
try {
userdao.updateUser(userName, map);
returnMap.put("statuscode", 0);
returnMap.put("message", "修改成功。");
} catch (Exception e) {
returnMap.put("statuscode", 1);
returnMap.put("message", "修改失败。");
} finally {
return returnMap;
}
}
@Override
public Map deleteUser(String name) {
Map returnMap = new HashMap();
try {
userdao.deleteUser(name);
returnMap.put("statuscode", 0);
returnMap.put("message", "删除成功。");
} catch (Exception e) {
returnMap.put("statuscode", 1);
returnMap.put("message", "删除失败。");
} finally {
return returnMap;
}
}
}
控制层
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import com.et.psm.service.UserService;
@RestController
public class UserController {
@Autowired
UserService userService;
@GetMapping("/user")
public Map validUser(String userName, String password) {
return userService.queryUserByName(userName, password);
}
@PostMapping("/user")
public Map addUser(@RequestBody Map map) {
return userService.addUser(map);
}
@PutMapping("/user/{userName}")
public Map UpdateUser(@PathVariable String userName, @RequestBody Map map) {
return userService.updateUser(userName, map);
}
@DeleteMapping("/user/{userName}")
public Map deleteUser(@PathVariable String userName) {
return userService.deleteUser(userName);
}
}
运行类,添加@EnableDiscoveryClient
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
@SpringBootApplication
@EnableDiscoveryClient
public class UserServiceMain {
public static void main(String[] args) {
SpringApplication.run(UserServiceMain.class, args);
}
}
运行项目,再访问http://localhost:8761 会看到已经有服务注册到注册中心了
Service Consumer
引入依赖
<!-- 客户端 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
<!-- ribbon 服务调用和负载均衡 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-ribbon</artifactId>
</dependency>
ribbon:使用RestTemplate实现http调用 提供了客户端负载均衡的能力
运行类:@RibbonClient(value="USERSERVICE")指定客户端主机名
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.netflix.ribbon.RibbonClient;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;
@SpringBootApplication
@EnableDiscoveryClient
@RibbonClient(value="USERSERVICE")
public class UserViewMain {
@LoadBalanced // 启动负载均衡
@Bean
RestTemplate restTemplate() {
return new RestTemplate();
}
public static void main(String[] args) {
SpringApplication.run(UserViewMain.class, args);
}
}
控制层:
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
@RestController
public class UserController {
@Autowired
RestTemplate template;
@RequestMapping("/validUser")
public String validUser(String userName, String password) {
Map returnCode = template
.getForObject("http://USERSERVICE/user?userName=" + userName + "&password=" + password, Map.class);
return (String) returnCode.get("message");
}
@PostMapping("/user")
public String addUser(@RequestParam Map map) {
// 模拟请求头
HttpHeaders requestHeaders = new HttpHeaders();
// 传递的json
requestHeaders.setContentType(MediaType.APPLICATION_JSON_UTF8);
// 返回可接受json类型
requestHeaders.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON_UTF8));
// 将请求头和请求体(表单数据)打包
HttpEntity<Map> request = new HttpEntity<Map>(map, requestHeaders);
Map returnCode = template.postForObject("http://USERSERVICE/user", request, Map.class);
return (String) returnCode.get("message");
}
@PutMapping("/user/{userName}")
public String updateUser(@PathVariable String userName,@RequestParam Map map) {
// 模拟请求头
HttpHeaders requestHeaders = new HttpHeaders();
// 传递的json
requestHeaders.setContentType(MediaType.APPLICATION_JSON_UTF8);
// 返回可接受json类型
requestHeaders.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON_UTF8));
// 将请求头和请求体(表单数据)打包
HttpEntity<Map> request = new HttpEntity<Map>(map, requestHeaders);
Map variables = new HashMap();
variables.put("userName", userName);
try {
template.put("http://USERSERVICE/user/{userName}", request, variables);
return "修改成功。";
} catch (Exception e) {
return "修改失败。";
}
}
@DeleteMapping("/user/{userName}")
public String deleteUser(@PathVariable String userName) {
Map variables = new HashMap();
variables.put("userName", userName);
try {
template.delete("http://USERSERVICE/user/{userName}", variables);
return "删除成功。";
} catch (Exception e) {
return "删除失败。";
}
}
}
注意:这里使用的是restful风格设计,所以在编写jsp文件的修改删除的时候需要加一个隐藏表单域,提交时要先提交到js进行处理,不然无法访问到控制层的修改或删除方法,如下
<%@ page language="java"
pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<title>Insert title here3</title>
<script type="text/javascript">
function sub(){
var userName=document.getElementsByName("userName")[0].value;
document.forms[0].action="user/"+userName;
document.forms[0].submit();
}
</script>
</head>
<body>
<form action="/user" method="post">
<input type="hidden" name="_method" value="delete">
用户名 :<input name="userName"/>
<input type="button" οnclick="sub()" value="删除"/>
</form>
</body>
</html>
现在整个实例已经完成了,在Service Consumer 里面编写jsp文件访问控制层里的crdu方法进行操作,当注册多个客户端时,调用服务将会轮询调用客户端的服务。