Spring Cloud 基础
姚新霁
2023-12-01
Spring cloud 实现服务注册(分布式、负载均衡、微服务集群)
基础服务组件(配置文件存放)
服务组件(数据库应用服务器)
web服务器(SpringMVC/servlet/api网关)
|
↑↓
↑
|
服务发现|注册
服务发现
←———————————→eureka服务器—————————
服务注册/发现
注释:API网关是一个服务器,是系统的唯一入口。系统的后端总入口。有以下两种方式:
单节点API网关:单节点的API网关为每个客户端提供不同的API,而不是提供一种万能风格的API。
Backends for frontends网关:这种模式是针对不同的客户端来实现一个不同的API网关
目前流行的网关:
Tyk
开放源码的API网关,它是快速、可扩展和现代的。Tyk提供了一个API管理平台,其中包括API网关、API分析、开发人员门户和API管理面板。Try 是一个基于Go实现的网关服务。
Kong
可扩展的开放源码API Layer(也称为API网关或API中间件)。Kong 在任何RESTful API的前面运行,通过插件扩展,它提供了超越核心平台的额外功能和服务。
Orange
和Kong类似也是基于OpenResty的一个API网关程序,是由国人开发的,学姐也是贡献者之一。
Netflix/zuul
Zuul是一种提供动态路由、监视、弹性、安全性等功能的边缘服务。Zuul是Netflix出品的一个基于JVM路由和服务端的负载均衡器。
apiaxle
Nodejs实现的一个API网关。
api-umbrella
Ruby实现的一个API网关。
第一步创建git工程
cloud-config-repo
配置文件存放的文件夹
cloud-simple-service
数据库应用服务器1
cloud-simple-service
数据库应用服务器2
cloud-config-server
配置管理服务器
cloud-eureka-server
eureka注册服务器
cloud-simple-ui
客户端
.gitignore
默认配置
README.md
说明文档
第二步配置pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<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>cloud</groupId>
<artifactId>cloud-config-server</artifactId>
<version>0.0.1</version>
<packaging>jar</packaging>
<name>cloud-config-serve</name>
<description>cloud-config-server</description>
<parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-parent</artifactId>
<version>Brixton.RELEASE</version>
<relativePath /> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>1.7</java.version>
</properties>
<repositories>
<repository>
<snapshots>
<enabled>true</enabled>
</snapshots>
<id>public</id>
<name>Public Repositories</name>
<url>http://maven.aliyun.com/nexus/content/groups/public/</url>
</repository>
<repository>
<id>spring-snapshots</id>
<name>Spring Snapshots</name>
<url>http://repo.spring.io/libs-snapshot-local</url>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>http://repo.spring.io/libs-milestone-local</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
<repository>
<id>spring-releases</id>
<name>Spring Releases</name>
<url>http://repo.spring.io/libs-release-local</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>public</id>
<name>Public Repositories</name>
<url>http://maven.aliyun.com/nexus/content/groups/public/</url>
</pluginRepository>
</pluginRepositories>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
<defaultGoal>compile</defaultGoal>
</build>
</project>
第三步:在cloud-config-repo目录下创建两个配置文件
cloud-config-dev.properties
//开发环境配置文件
cloud-config-test.properties
//测试环境配置信息,目前都相同吧
mysqldb.datasource.url=jdbc\:mysql\://123.207.16.155\:3306/test?useUnicode\=true&characterEncoding\=utf-8
mysqldb.datasource.username=csst
mysqldb.datasource.password=csst
logging.level.org.springframework.web:DEBUG
第四步:创建配置管理服务器cloud-config-server
ConfigServerApplication.java
@SpringBootApplication
@EnableConfigServer//激活该应用为配置文件服务器
public class ConfigServerApplication {
public static void main(String[] args) {
SpringApplication.run(ConfigServerApplication.class, args);
}
}
application.properties
#配置当前 web 应用绑定 8888 端口
server.port=8888
#指定配置文件所在的 git 工程路径
spring.cloud.config.server.git.uri=https://gitee.com/anlemusic/Test.git
#searchPaths表示将搜索该文件夹下的配置文件
spring.cloud.config.server.git.searchPaths=cloud-config-repo
pom.xml
最后,还需要在 pom 文件中增加配置服务器的相关依赖:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-server</artifactId>
</dependency>
第五步:创建数据库
CREATE TABLE `user` (`id` varchar(50) NOT NULL DEFAULT '',
`username` varchar(50) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
第六步:cloud-simple-service的数据库应用的配置(执行时两次,不同的端口)
DataSourceProperties.java
package cloud.simple.service.conf;
import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties(prefix = DataSourceProperties.DS, ignoreUnknownFields = false)
public class DataSourceProperties {
//对应配置文件里的配置键
public final static String DS="mysqldb.datasource";
private String driverClassName ="com.mysql.jdbc.Driver";
private String url;
private String username;
private String password;
private int maxActive = 100;
private int maxIdle = 8;
private int minIdle = 8;
private int initialSize = 10;
private String validationQuery;
public String getDriverClassName() {
return driverClassName;
}
public void setDriverClassName(String driverClassName) {
this.driverClassName = driverClassName;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public int getMaxActive() {
return maxActive;
}
public void setMaxActive(int maxActive) {
this.maxActive = maxActive;
}
public int getMaxIdle() {
return maxIdle;
}
public void setMaxIdle(int maxIdle) {
this.maxIdle = maxIdle;
}
public int getMinIdle() {
return minIdle;
}
public void setMinIdle(int minIdle) {
this.minIdle = minIdle;
}
public int getInitialSize() {
return initialSize;
}
public void setInitialSize(int initialSize) {
this.initialSize = initialSize;
}
public String getValidationQuery() {
return validationQuery;
}
public void setValidationQuery(String validationQuery) {
this.validationQuery = validationQuery;
}
public boolean isTestOnBorrow() {
return testOnBorrow;
}
public void setTestOnBorrow(boolean testOnBorrow) {
this.testOnBorrow = testOnBorrow;
}
public boolean isTestOnReturn() {
return testOnReturn;
}
public void setTestOnReturn(boolean testOnReturn) {
this.testOnReturn = testOnReturn;
}
private boolean testOnBorrow = false;
private boolean testOnReturn = false;
}
MybatisDataSource.java
package cloud.simple.service.conf;
import javax.annotation.PreDestroy;
import javax.sql.DataSource;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
@Configuration
@EnableConfigurationProperties(DataSourceProperties.class)
//mybaits dao 搜索路径
@MapperScan("cloud.simple.service.dao")
public class MybatisDataSource {
@Autowired
private DataSourceProperties dataSourceProperties;
//mybaits mapper xml搜索路径
private final static String mapperLocations="classpath:cloud/simple/service/dao/*.xml";
private org.apache.tomcat.jdbc.pool.DataSource pool;
@Bean(destroyMethod = "close")
public DataSource dataSource() {
DataSourceProperties config = dataSourceProperties;
this.pool = new org.apache.tomcat.jdbc.pool.DataSource();
this.pool.setDriverClassName(config.getDriverClassName());
this.pool.setUrl(config.getUrl());
if (config.getUsername() != null) {
this.pool.setUsername(config.getUsername());
}
if (config.getPassword() != null) {
this.pool.setPassword(config.getPassword());
}
this.pool.setInitialSize(config.getInitialSize());
this.pool.setMaxActive(config.getMaxActive());
this.pool.setMaxIdle(config.getMaxIdle());
this.pool.setMinIdle(config.getMinIdle());
this.pool.setTestOnBorrow(config.isTestOnBorrow());
this.pool.setTestOnReturn(config.isTestOnReturn());
this.pool.setValidationQuery(config.getValidationQuery());
return this.pool;
}
@PreDestroy
public void close() {
if (this.pool != null) {
this.pool.close();
}
}
@Bean
public SqlSessionFactory sqlSessionFactoryBean() throws Exception {
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
sqlSessionFactoryBean.setDataSource(dataSource());
PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
sqlSessionFactoryBean.setMapperLocations(resolver.getResources(mapperLocations));
return sqlSessionFactoryBean.getObject();
}
@Bean
public PlatformTransactionManager transactionManager() {
return new DataSourceTransactionManager(dataSource());
}
}
UserDao.java
package cloud.simple.service.dao;
import java.util.List;
import cloud.simple.service.model.User;
public interface UserDao {
List<User> findAll();
}
UserService.java
package cloud.simple.service.domain;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import cloud.simple.service.dao.UserDao;
import cloud.simple.service.model.User;
@Service
@Transactional
public class UserService {
@Autowired
private UserDao userMapper;
public List<User> searchAll(){
List<User> list = userMapper.findAll();
return list;
}
}
User.java
package cloud.simple.service.model;
public class User {
private String username;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
}
UserController.java
package cloud.simple.service.web;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import cloud.simple.service.domain.UserService;
import cloud.simple.service.model.User;
@RestController
public class UserController {
@Autowired
UserService userService;
@RequestMapping(value="/user",method=RequestMethod.GET)
public List<User> readUserInfo(){
List<User> ls=userService.searchAll();
return ls;
}
}
src\main\resources\cloud\simple\service\dao
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cloud.simple.service.dao.UserDao">
<select id="findAll" resultType="cloud.simple.service.model.User">
select * from user
</select>
</mapper>
src\main\resources\bootstrap.properties
#指定远程加载配置信息的地址
#${config.port:8888}如果在命令行提供了config.port 参数,我们就用这个端口,否则就用 8888 端口
spring.cloud.config.uri=http://127.0.0.1:${config.port:8888}
#配置文件名称config.name为simple-service,config.profile为dev.即{application}-{profile}.properties=cloud-config-dev.properties
spring.cloud.config.name=cloud-config
spring.cloud.config.profile=${config.profile:dev}
#service discovery url
eureka.client.serviceUrl.defaultZone=http\://localhost\:8761/eureka/,http\://zlhost\:8762/eureka/
#service name
spring.application.name=cloud-simple-service
server.port=8081
第七步:创建cloud-eureka-server 注册服务器
所有的服务端及访问服务的客户端都需要连接到注册管理器( eureka 服务器)。服务在启动时
会自动注册自己到 eureka 服务器,每一个服务都有一个名字,这个名字会被注册到 eureka 服务
器。使用服务的一方只需要使用该名字加上方法名就可以调用到服务。
pom.xml
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
</dependencies>
EurekaServer.java
@SpringBootApplication
//使用@EnableEurekaServer 注解就可以让应用变为 Eureka 服务器
@EnableEurekaServer
public class EurekaServer {
public static void main(String[] args) {
SpringApplication.run(EurekaServer.class, args);
}
}
application.properties
#server.port 配置 eureka 服务器端口号
server.port=8761
eureka.instance.hostname=localhost
#是否注册自身到 eureka 服务器
eureka.client.registerWithEureka=false
#是否从 eureka 服务器获取注册信息
eureka.client.fetchRegistry=false
#设置 eureka 服务器所在的地址,查询服务和注册服务都需要依赖这个地址。
eureka.client.serviceUrl.defaultZone=http://${eureka.instance.hostname}:${server.port}/eureka/
#manager url:http://localhost:8761/
//其他服务使用eureka服务器,只需添加@EnableDiscoveryClient注解就可以了
//然后在其配置文件中添加:
//eureka.client.serviceUrl.defaultZone=http\://localhost\:8761/eureka/
//spring.application.name=cloud-simple-service
//pom 文件也需要增加:
//<dependency>
//<groupId>org.springframework.cloud</groupId>
//<artifactId>spring-cloud-starter-eureka</artifactId>
//</dependency>
第八步:创建cloud-simple-ui客户端
在spring boot中已经不推荐使用jsp,所以你如果使用jsp来实现ui端将会很麻烦.
这可能跟现在的开发主流偏重移动端有关,跟微服务有关,跟整个时代当前的技术需求有关。
单纯以html来作为客户端,有很多好处.比如更利于使用高速缓存、使后台服务无状态话、更利于处理高并发、更利于页面作为服务、小服务组合成大服务等。
User.java
package cloud.simple.model;
public class User {
private String username;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
}
WebApplication.java
@SpringBootApplication
//配置本应用将使用服务注册和服务发现
@EnableEurekaClient
//示启用断路器,断路器依赖于服务注册和发现
@EnableHystrix
public class WebApplication {
public static void main(String[] args) throws Exception {
SpringApplication.run(WebApplication.class, args);
}
}
UserService.java
@Service
public class UserService {
@Autowired
RestTemplate restTemplate;//restTemplate进行服务调用
final String SERVICE_NAME="cloud-simple-service";
//断路器的基本作用就是@HystrixCommand注解的方法失败后,系统将自动切换到fallbackMethod方法执行(回退机制)
//使用该功能允许快速失败并迅速恢复或者回退并优雅降级
@HystrixCommand(fallbackMethod = "fallbackSearchAll")
public List<User> searchAll() {
return restTemplate.getForObject("http://"+SERVICE_NAME+"/user", List.class);
}
private List<User> fallbackSearchAll() {
System.out.println("HystrixCommand fallbackMethod handle!");
List<User> ls = new ArrayList<User>();
User user = new User();
user.setUsername("TestHystrix");
ls.add(user);
return ls;
}
}
UserController.java
@RestController
public class UserController {
@Autowired
UserService userService;
@RequestMapping(value="/users")
public ResponseEntity<List<User>> readUserInfo(){
List<User> users=userService.searchAll();
return new ResponseEntity<List<User>>(users,HttpStatus.OK);
} }
index.html
<html ng-app="users">
<head>
<script src="http://cdn.bootcss.com/angular.js/1.3.18/angular.min.js"></script>
<script src="http://cdn.bootcss.com/angular.js/1.3.18/angular-route.js"></script>
<script src="js/app.js"></script>
</head>
<body>
<div ng-view class="container">
</div>
</body>
</html>
user-page.html
<div>
<ul ng-controller="userCtr">
<li ng-repeat="item in userList">
{{item.username}}
</li>
</ul>
</div>
app.js
angular.module('users', ['ngRoute']).config(function ($routeProvider) {
$routeProvider.when('/', {
templateUrl: 'user-page.html',
controller: 'userCtr'
})
}).controller('userCtr', function ($scope, $http) {
$http.get('users').success(function (data) {
//alert(data+"");
$scope.userList = data;
});
});
//使用了angularJS库
bootstrap.properties
spring.cloud.config.uri=http://127.0.0.1:${config.port:8888}
spring.cloud.config.name=cloud-config
spring.cloud.config.profile=${config.profile:dev}
eureka.client.serviceUrl.defaultZone=http\://localhost\:8761/eureka/
spring.application.name=simple-ui-phone
pom.xml(因为使用了断路器,加入hystrix依赖)
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix</artifactId>
</dependency>
log4j.properties
log4j.rootCategory=INFO, CONSOLE
PID=????
LOG_PATTERN=[%d{yyyy-MM-dd HH:mm:ss.SSS}] log4j%X{context} - ${PID} %5p [%t] --- %c{1}: %m%n
# CONSOLE is set to be a ConsoleAppender using a PatternLayout.
log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender
log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout
log4j.appender.CONSOLE.layout.ConversionPattern=${LOG_PATTERN}
log4j.category.org.hibernate.validator.internal.util.Version=INFO
log4j.category.org.apache.coyote.http11.Http11NioProtocol=INFO
log4j.category.org.apache.tomcat.util.net.NioSelectorPool=INFO
log4j.category.org.apache.catalina.startup.DigesterFactory=INFO
log4j.logger.org.apache=INFO
剩下最重要的事情就是进行部署了,我们以此使用命令启动这些服务及应用:
java -jar cloud-config-server-0.0.1.jar
启动配置服务器,固定绑定端口8888
java -jar cloud-eureka-server-1.0.0.jar
启动注册服务器,固定绑定端口8671
java -jar cloud-simple-service-1.0.0.jar --server.port=8081 >log8081.log
启动后台服务,绑定端口8081
java -jar cloud-simple-service-1.0.0.jar --server.port=8082 >log8082.log
启动后台服务,绑定端口8082
java -jar cloud-simple-ui-1.0.0.jar --server.port=8080 >log8080.log
启动前端 ui 应用,绑定端口8080
运行 http://localhost:8080/即可看到运行的结果。其中“>log8080.log”表示输出日志到文件。
这里运行了两个cloud-simple-service实例,主要是为了演示ribbon负载均衡。默认情况下使用
ribbon不需要再作任何配置,不过它依赖于注册服务器。
docker发布spring cloud应用
Docker是一种虚拟机技术,在 linux 虚拟机技术 LXC 基础上又封装了一层,是基于 LXC 的容器技术。
可以把容器看做是一个简易版的 Linux 环境(包括 root 用户权限、进程空间、用户空间和网络空间等)
和运行在其中的应用程序。容器是用来装东西的,Docker 可以装载应用本身及其运行环境进容器
这是一个很小的文件,然后把这个文件扔到任何兼容的服务器上就可以运行,也是基于这一点
Docker 可以同时让应用的部署、测试和分发都变得前所未有的高效和轻松!
创建cloud-simple-docker一个简单的spring boot应用
MusicApplication.java
@SpringBootApplication
@RestController
public class MusicApplication {
@RequestMapping("/")
public String home() {
return "Hello Docker World";
}
public static void main(String[] args) {
SpringApplication.run(MusicApplication.class, args);
}
}
创建src/main/docker/Dockerfile文件
#------------------------------------------------------------------------------------
# 基于那个镜像
FROM daocloud.io/java:8
# 将本地文件夹挂载到当前容器(tomcat使用)
VOLUME /tmp
# 拷贝文件到容器
ADD cloud-simple-docker-1.0.0.jar /app.jar
# 打开服务端口
EXPOSE 8080
# 配置容器启动后执行的命令
RUN bash -c 'touch /app.jar'
ENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom","-jar","/app.jar"]
#------------------------------------------------------------------------------------
Pom.xml
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.3.5.RELEASE</version>
</parent>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Brixton.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<properties>
<docker.image.prefix>springio</docker.image.prefix>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.7</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
部署docker
yum -y install docker-io(Linux请先安装)
将编译后的 jar 文件考到服务器某个目录,这里是 tmp 目录。然后将 Dockerfile 也考到该目
录,最后进入到该目录下运行命令(后面有个点):docker build -t local/cloud-simple-docker .
(此处后应内核版本低没有亲自尝试)
运行成功后,就创建了一个镜像,可以使用 docker images 来查看该镜像。
有了镜像就可以运行了,使用下面命令运行:
docker run -p 8080:8080 –t local/cloud-simple-docker
其中 8080:8080 表示本机端口映射到 Docker 实例端口
成功后可以使用 docker ps –a查看镜像运行情况:
local/cloud-simple-docker | latest | 3ef51d55eb27 | 22 minutes ago | 667.2 MB
可以看到这个包括了 java 运行环境的 web 应用镜像是667MB
使用spring boot和thrift、zookeeper建立微服务
thrift:由Facebook开发的远程服务调用框架Apache Thrift,它采用接口描述语言定义并创建服务,支持可扩展的跨语言服务开发
所包含的代码生成引擎可以在多种语言中,如 C++, Java, Python, PHP, Ruby, Erlang, Perl, Haskell, C#, Cocoa, Smalltalk
等创建高效的、无缝的服务,其传输数据采用二进制格式,相对 XML 和 JSON 体积更小,对于高并发、大数据量和多语言的环境更有优势。
zookeeper:
cloud-thrift-interface
接口及传输对象定义
cloud-thrift-server
服务提供方
cloud-thrift-client
服务调用方
第一步:cloud-thrift-interface
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>cloud.simple</groupId>
<artifactId>cloud-thrift-interface</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>cloud-thrift-interface</name>
<url>http://maven.apache.org</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>1.7</java.version>
</properties>
<repositories>
<repository>
<snapshots>
<enabled>true</enabled>
</snapshots>
<id>public</id>
<name>Public Repositories</name>
<url>http://maven.aliyun.com/nexus/content/groups/public/</url>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>public</id>
<name>Public Repositories</name>
<url>http://maven.aliyun.com/nexus/content/groups/public/</url>
</pluginRepository>
</pluginRepositories>
<dependencies>
<dependency>
<groupId>org.apache.thrift</groupId>
<artifactId>libthrift</artifactId>
<version>0.9.1</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
<defaultGoal>compile</defaultGoal>
</build>
</project>
创建src\main\resources\UserService.thrift
namespace java cloud.simple.service
struct UserDto {
1: i32 id
2: string username
}
service UserService {
UserDto getUser()
}
http://apache.fayea.com/thrift/可下载随意版本,放入文件夹,加入环境变量
执行thrift -gen java %Path%UserService.thrift
接口定义完后,使用 thrift 命令生成对应的 java 文件,主要生成两个文件,分别是
UserService.java 和 UserDto.java,把这两个文件放入 cloud-thrift-interface 工程,因为客户端也需
要这个接口定义。
第二步:cloud-thrift-server
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>cloud.simple</groupId>
<artifactId>cloud-thrift-server</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>cloud-thrift-server</name>
<url>http://maven.apache.org</url>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.3.3.RELEASE</version>
<relativePath /> <!-- lookup parent from repository -->
</parent>
<repositories>
<repository>
<snapshots>
<enabled>true</enabled>
</snapshots>
<id>public</id>
<name>Public Repositories</name>
<url>http://maven.aliyun.com/nexus/content/groups/public/</url>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>public</id>
<name>Public Repositories</name>
<url>http://maven.aliyun.com/nexus/content/groups/public/</url>
</pluginRepository>
</pluginRepositories>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>1.7</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.thrift</groupId>
<artifactId>libthrift</artifactId>
<version>0.9.1</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.1</version>
</dependency>
<!-- 引入cloud-thrift-interface -->
<dependency>
<groupId>cloud.simple</groupId>
<artifactId>cloud-thrift-interface</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<!-- zookeeper -->
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.4.6</version>
</dependency>
<dependency>
<groupId>org.apache.helix</groupId>
<artifactId>helix-core</artifactId>
<version>0.6.4</version>
</dependency>
<!-- zookeeper -->
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
<defaultGoal>compile</defaultGoal>
</build>
</project>
ThriftConfig.java
@Configuration
public class ThriftConfig {
// 创建一个使用单个 worker 线程的 Executor,以无界队列方式来运行该线程
ExecutorService executor = Executors.newSingleThreadExecutor();
@Bean
public TServerTransport tServerTransport() {
try {
return new TServerSocket(7911);
} catch (TTransportException e) {
e.printStackTrace();
}
return null;
}
@Bean
public TServer tServer() {
// 在服务的提供端需要实现接口,并且还要把实现类注册到 thrift 服务器。
UserService.Processor processor = new UserService.Processor(new UserServiceImpl());
// UserServiceImpl 就是接口实现类,将其注册为Tserver。
TServer server = new TThreadPoolServer(new TThreadPoolServer.Args(tServerTransport()).processor(processor));
return server;
}
// 注册完服务后,需要启动 TServer,很显然这个需要在线程里启动。
@PostConstruct
public void init() {
executor.execute(new Runnable() {
@Override
public void run() {
tServer().serve();
}
});
}
}
使用zookeeper进行服务名称注册
上面是注册具体的服务执行类,这一步是将服务的实例注册进 zookeeper,这样才能实现负载均
衡。让客户端可以根据服务实例列表选择服务来执行。当然这里只需要注册服务所在服务器的 IP
即可,因为客户端只要知道 IP,也就知道访问那个 IP 下的该服务。
ZooKeeperConfig.java
@Configuration
public class ZooKeeperConfig {
@Value("${service.name}")
String serviceName;
@Value("${zookeeper.server.list}")
String serverList;
ExecutorService executor = Executors.newSingleThreadExecutor();
@PostConstruct
public void init() {
executor.execute(new Runnable() {
@Override
public void run() {
registService();
try {
Thread.sleep(1000 * 60 * 60 * 24 * 360 * 10);// 休眠十年(注册服务十年)
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
// 注册服务
public ZkClient registService() {
String servicePath = "/" + serviceName;// 根节点路径
ZkClient zkClient = new ZkClient(serverList);
boolean rootExists = zkClient.exists(servicePath);
if (!rootExists) {
zkClient.createPersistent(servicePath);
}
InetAddress addr = null;
try {
addr = InetAddress.getLocalHost();
} catch (UnknownHostException e) {
e.printStackTrace();
}
String ip = addr.getHostAddress().toString();
String serviceInstance = System.nanoTime() + "-" + ip;
// zkClient.createEphemeral建立临时节点,如果这台服务器宕机,这个临时节点是会被清除的,这样客户端在访问时就不会再选择该服务器上的服务。
zkClient.createEphemeral(servicePath + "/" + serviceInstance);
System.out.println("提供的服务为:" + servicePath + "/" + serviceInstance);
return zkClient;
}
}
application.properties
service.name=cloud-thrift-service
zookeeper.server.list=127.0.0.1\:2181
第三步:cloud-thrift-client
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>cloud.simple</groupId>
<artifactId>cloud-thrift-client</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>cloud-thrift-client</name>
<url>http://maven.apache.org</url>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.3.3.RELEASE</version>
<relativePath /> <!-- lookup parent from repository -->
</parent>
<repositories>
<repository>
<snapshots>
<enabled>true</enabled>
</snapshots>
<id>public</id>
<name>Public Repositories</name>
<url>http://maven.aliyun.com/nexus/content/groups/public/</url>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>public</id>
<name>Public Repositories</name>
<url>http://maven.aliyun.com/nexus/content/groups/public/</url>
</pluginRepository>
</pluginRepositories>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>1.7</java.version>
</properties>
<dependencies>
<!--web应用基本环境配置 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--监控基本环境配置 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>cloud.simple</groupId>
<artifactId>cloud-thrift-interface</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<!-- zookeeper -->
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.4.6</version>
</dependency>
<dependency>
<groupId>org.apache.helix</groupId>
<artifactId>helix-core</artifactId>
<version>0.6.4</version>
</dependency>
<!-- zookeeper -->
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
<defaultGoal>compile</defaultGoal>
</build>
</project>
ZooKeeperConfig.java
@Configuration
public class ZooKeeperConfig {
@Value("${service.name}")
String serviceName;
@Value("${zookeeper.server.list}")
String zookeeperList;
ExecutorService executor = Executors.newSingleThreadExecutor();
// thrift实例列表
public static Map<String, UserService.Client> serviceMap = new HashMap<String, UserService.Client>();
@PostConstruct
private void init() {
executor.execute(new Runnable() {
@Override
public void run() {
startZooKeeper();
try {
Thread.sleep(1000 * 60 * 60 * 24 * 360 * 10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
// 注册服务
private void startZooKeeper() {
List<String> currChilds = new ArrayList<String>();
String servicePath = "/" + serviceName;// 根节点路径
ZkClient zkClient = new ZkClient(zookeeperList);
boolean serviceExists = zkClient.exists(servicePath);
if (serviceExists) {
currChilds = zkClient.getChildren(servicePath);
} else {
throw new RuntimeException("service not exist!");
}
for (String instanceName : currChilds) {
// 没有该服务,建立该服务
if (!serviceMap.containsKey(instanceName)) {
serviceMap.put(instanceName, createUserService(instanceName));
}
}
// 注册事件监听
zkClient.subscribeChildChanges(servicePath, new IZkChildListener() {
// @Override
public void handleChildChange(String parentPath, List<String> currentChilds) throws Exception {
// 实例(path)列表:当某个服务实例宕机,实例列表内会减去该实例
for (String instanceName : currentChilds) {
// 没有该服务,建立该服务
if (!serviceMap.containsKey(instanceName)) {
serviceMap.put(instanceName, createUserService(instanceName));
}
}
for (Map.Entry<String, UserService.Client> entry : serviceMap.entrySet()) {
// 该服务已被移除
if (!currentChilds.contains(entry.getKey())) {
UserService.Client c = serviceMap.get(entry.getKey());
try {
c.getInputProtocol().getTransport().close();
c.getOutputProtocol().getTransport().close();
} catch (Exception e) {
e.printStackTrace();
}
serviceMap.remove(entry.getKey());
}
}
System.out.println(parentPath + "事件触发");
}
});
}
// 创建一个服务实例
private UserService.Client createUserService(String serviceInstanceName) {
String ip = serviceInstanceName.split("-")[1];
TSocket transport = new TSocket(ip, 7911);
try {
transport.open();
} catch (TTransportException e) {
e.printStackTrace();
}
return new UserService.Client(new TBinaryProtocol(transport));
}
}
UserServiceProvider.java
@Component
public class UserServiceProvider {
public UserService.Client getBalanceUserService(){
Map<String, UserService.Client> serviceMap =ZooKeeperConfig.serviceMap;
//以负载均衡的方式获取服务实例
for (Map.Entry<String, UserService.Client> entry : serviceMap.entrySet()) {
System.out.println("可供选择服务:"+entry.getKey());
}
int rand=new Random().nextInt(serviceMap.size());
String[] mkeys = serviceMap.keySet().toArray(new String[serviceMap.size()]);
return serviceMap.get(mkeys[rand]);
}
}
UserController.java
@Controller
public class UserController {
@Autowired
UserServiceProvider userServiceProvider;
@ResponseBody
@RequestMapping(value = "/hello")
String hello() throws TException {
UserService.Client svr=userServiceProvider.getBalanceUserService();
UserDto userDto= svr.getUser();
return "hi "+userDto.getUsername();
}
}//最后控制层实现数据获取
spring boot 自动部署方案
现在主流的自动部署方案大都是基于Docker的了,但传统的自动部署方案比较适合中小型公司,下面的方案就是比较传统的自动部署方案
自动部署一般都是通过以下步骤进行的。
首选由持续性集成工具进行自动编译产生项目的输出,对于我们来说也就是 jar 包。
然后该 jar 经过测试就可以分发到各个服务器,各个服务器的监控脚本监控到该新版本
自动停止旧实例重新运行新实例。
详细版本:
Jenkins 编译的结果需要暂时存放,以便于测试人员拉取进行测试。这里存放在 maven 库中。
测试通过后也需要手动推送到生产环境,因为不可能每个版本都推送到生产环境。生产环境需要一
台 FTP 或 GIT、 SVN Server 作为中转机,暂存打包的应用,然后生产的服务器通过脚本轮询该中
转机获得新的版本。获得新的版本后,自动停止旧的版本,运行新的版本。
spring boot/cloud 应用监控
编写监控程序:cloud-monitor-server
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<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>cloud</groupId>
<artifactId>cloud-monitor-server</artifactId>
<version>1.0.0</version>
<name>cloud-monitor-server</name>
<packaging>jar</packaging>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.3.5.RELEASE</version>
</parent>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Brixton.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<repositories>
<repository>
<snapshots>
<enabled>true</enabled>
</snapshots>
<id>public</id>
<name>Public Repositories</name>
<url>http://maven.aliyun.com/nexus/content/groups/public/</url>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>public</id>
<name>Public Repositories</name>
<url>http://maven.aliyun.com/nexus/content/groups/public/</url>
</pluginRepository>
</pluginRepositories>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>1.7</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
<dependency>
<groupId>de.codecentric</groupId>
<artifactId>spring-boot-admin-server</artifactId>
<version>1.3.3</version>
</dependency>
<dependency>
<groupId>de.codecentric</groupId>
<artifactId>spring-boot-admin-server-ui</artifactId>
<version>1.3.3</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
<defaultGoal>compile</defaultGoal>
</build>
</project>
SpringBootAdminApplication
@Configuration
@EnableAutoConfiguration
@EnableDiscoveryClient
@EnableAdminServer
public class SpringBootAdminApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBootAdminApplication.class, args);
}
}
application.properties
server.port=8050
spring.application.name=@pom.artifactId@
endpoints.health.sensitive=false
eureka.instance.leaseRenewalIntervalInSeconds=5
#指定了服务注册中心的位置
eureka.client.serviceUrl.defaultZone=http://localhost:8761/eureka/
#在默认设置下,Eureka服务注册中心也会将自己作为客户端来尝试注册它自己,所以我们需要禁用它的客户端注册行为。
eureka.client.register-with-eureka=false
eureka.client.fetch-registry=false
logback-spring.xml
//当前日志级别管理仅限 Logback
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<include resource="org/springframework/boot/logging/logback/base.xml"/>
<jmxConfigurator/>
</configuration>
被监听程序需要先添加依赖
pom.xml
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
application.properties
spring.boot.admin.url=http://localhost:8050
#为了在 Spring Boot Admin 的应用管理列表显示被管理应用的版本号,你需要设置 info.version
info.version=@project.version@
ClientEurekaSampleApplication.java
@SpringBootApplication
//添加 @EnableDiscoveryClient 或 @EnableEurekaClient 注解到启动类上,将自己注册到 Erueka Server。
@EnableEurekaClient
public class ClientEurekaSampleApplication {
public static void main(String[] args) {
SpringApplication.run(ClientEurekaSampleApplication.class, args);
}
}
logback.xml(与上者平级)
<configuration>
<include resource="org/springframework/boot/logging/logback/base.xml"/>
<jmxConfigurator/>
</configuration>
//https://blog.csdn.net/kinginblue/article/details/52132113可参考
hystrix-turbine 监控的使用