- RPC与SOA
- CAP原则/定理
- Zookeeper
- Dubbo
- Assembly插件
- Nginx
- vsftpd
- Redis
- JSONP
- Solr
- Spring Cloud
RPC与SOA
1.单体应用架构
在访问流量很小时,一般将所有功能都部署在一起,以减少部署节点和成本,此时负责简化CURD的ORM是关键
2.垂直应用架构
在访问量增加,一般将应用拆分成不相干的几个应用,此时MVC是关键
3.分布式服务架构
随着垂直应该用不断增多,将核心业务抽取出来作为独立的服务,此时用于提高业务复用及整合的RPC是关键
4.流动计算架构
当服务越来越多,需要增加一个调度中心,此时用于提高机器利用率的SOA是关键
CAP原则/定理
CAP原则是指在一个分布式系统中,C【Consistency-数据一致性】、A【Availability-服务可用性】、P【Partition tolerance-分区容错性】三者不可兼得
Zookeeper
zookeeper是一个分布式应用程序协调服务组件,具有高可靠性、可扩展性、分布式及可配置的协调机制,可用于数据发布与订阅(注册中心)、集群管理与Master选举、命名服务、分布式通知与协调、负载均衡、分布式锁、分布式队列等
1.单机版安装
注意:zookeeper底层使用Java编写,所以使用zookeeper需要配置jdk环境变量(此处省略)
1.上传zookeeper压缩包并解压至指定目录
2.在zookeeper的根目录中新建data目录(用于存放数据)
3.复制zookeeper根目录下的conf目录下的zoo_sample.cfg,并将其改名为zoo.cfg
4.修改zoo.cfg中dataDir的值为新建的data目录的全限定路径
5.开放zookeeper访问端口
6.启动zookeeper
7.查看状态
Dubbo
Dubbo是一个分布式服务框架,致力于提供高性能和透明化的RPC远程服务调用方案和SOA服务治理方案
1.架构
1.服务容器Container负责启动、加载和运行服务提供者Provider
2.服务提供者Provider在启动时,向注册中心Registry注册自己所提供的服务
3.服务消费者Consumer在启动时,向注册中心Registry订阅自己所需的服务
4.注册中心Registry返回服务提供者Provider的地址列表给服务消费者Consumer
5.服务消费者Consumer从地址列表中选择并调用服务
6.服务提供者Provider和服务消费者Consumer定时每分钟发送一次在内存中累计调用次数和时间给监控中心Monitor
2.支持的注册中心
1.Zookeeper(Dubbo2.3.3开始)
<dubbo:registry address="zookeeper://192.168.54.130:2181" />
2.Multicast
3.Redis
4.Simple
3.支持的协议
1.Dubbo协议:采用单一长连接和NIO异步通讯,适合小数据量大并发的服务调用,以及服务消费者远多于服务提供者的情况,不适合传输文件、视频等
<dubbo:protocol name="dubbo" port="20880" />
2.RMI协议
3.Hessian协议
4.配置式开发
1.provider开发
<dependencies>
<!--测试依赖-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<!--dubbo依赖-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>dubbo</artifactId>
<version>2.6.0</version>
</dependency>
<!--zookeeper依赖-->
<dependency>
<groupId>com.101tec</groupId>
<artifactId>zkclient</artifactId>
<version>0.10</version>
</dependency>
<!--lombok依赖-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.10</version>
</dependency>
</dependencies>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://code.alibabatech.com/schema/dubbo
http://code.alibabatech.com/schema/dubbo/dubbo.xsd">
<!--配置服务名称-->
<dubbo:application name="provider"/>
<!--配置注册中心-->
<dubbo:registry address="zookeeper://192.168.54.130:2181"/>
<!--配置协议-->
<dubbo:protocol name="dubbo" port="20880"/>
<!--配置服务接口-->
<dubbo:service interface="cn.khue.service.UserService" ref="userServiceImpl"/>
<!--配置服务bean-->
<bean id="userServiceImpl" class="cn.khue.service.impl.UserServiceImpl"/>
</beans>
import lombok.NoArgsConstructor;
import lombok.AllArgsConstructor;
import lombok.Data;
import java.io.Serializable;
@Data
@AllArgsConstructor
@NoArgsConstaructor
public class User implements Serializable {
private int id;
private String username;
private String password;
}
import cn.khue.pojo.User;
public interface UserService {
User login(User user);
}
import cn.khue.pojo.User;
import cn.khue.service.UserService;
import org.springframework.stereotype.Service;
@Service
public class UserServiceImpl implements UserService {
@Override
public User login(User user) {
return new User(1,"admin","123");
}
}
import com.alibaba.dubbo.container.Main;
public class AppTest {
public static void main( String[] args ) {
Main.main(args);
}
}
2.consumer开发
<dependencies>
<!--测试依赖-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<!--dubbo依赖-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>dubbo</artifactId>
<version>2.6.0</version>
</dependency>
<!--zookeeper依赖-->
<dependency>
<groupId>com.101tec</groupId>
<artifactId>zkclient</artifactId>
<version>0.10</version>
</dependency>
<!--lombok依赖-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.10</version>
</dependency>
</dependencies>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://code.alibabatech.com/schema/dubbo
http://code.alibabatech.com/schema/dubbo/dubbo.xsd">
<!--配置服务名称-->
<dubbo:application name="consumer"/>
<!--配置注册中心-->
<dubbo:registry address="zookeeper://192.168.54.130:2181"/>
<!--配置服务接口-->
<dubbo:reference interface="cn.khue.service.UserService" id="userService"/>
</beans>
import lombok.NoArgsConstructor;
import lombok.AllArgsConstructor;
import lombok.Data;
import java.io.Serializable;
@Data
@AllArgsConstructor
@NoArgsConstaructor
public class User implements Serializable {
private int id;
private String username;
private String password;
}
import cn.khue.pojo.User;
public interface UserService {
User login(User user);
}
import org.junit.Test;
import cn.khue.pojo.User;
import cn.khue.service.UserService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class AppTest {
@Test
public void test() {
//获取配置文件
ApplicationContext ac=new ClassPathXmlApplicationContext("dubbo-consumer-config.xml");
//获取bean
UserService userService = ac.getBean("userServices", UserService.class);
//调用方法
User user = userService.login(new User());
//输出测试数据
System.out.println(user);
}
}
3.测试(启动远程的zookeeper,启动provider中测试的main方法,启动consumer中测试的test方法)
注意:
1.在IntelliJ IDEA中使用Lombok需要实现一下三步:安装Lombok的插件(Settings - Plugins);开启注解(Settings - Build,Execution,Deployment - Compiler - Annotation Processors - 勾选Enable annotation processiong);在Maven中导入Lombok的依赖
2.provider的配置文件必须放在/META-INF/spring/目录下
3.provider的服务接口全限定路径必须与consumer的服务接口的全限定路径一致(dubbo中服务的核心匹配在于判断Group、Version、Classifier是否相同)
5.注解式开发
1.Provider
修改provider-spring-config.xml
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://code.alibabatech.com/schema/dubbo
http://code.alibabatech.com/schema/dubbo/dubbo.xsd">
<!--配置服务名称-->
<dubbo:application name="provider"/>
<!--配置注册中心-->
<dubbo:registry address="zookeeper://192.168.54.130:2181"/>
<!--配置协议-->
<dubbo:protocol name="dubbo" port="20880"/>
<!--配置服务注解扫描-->
<dubbo:annotation package="cn.khue.service.impl" />
</beans>
使用dubbo下的@Service注解
import cn.khue.pojo.User;
import cn.khue.service.UserService;
import com.alibaba.dubbo.config.annotation.Service;
@Service
public class UserServiceImpl implements UserService {
@Override
public User login(User user) {
return new User(1,"admin","123");
}
}
2.Consumer
修改consumer-spring-config.xml
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://code.alibabatech.com/schema/dubbo
http://code.alibabatech.com/schema/dubbo/dubbo.xsd">
<!--配置服务名称-->
<dubbo:application name="consumer"/>
<!--配置注册中心-->
<dubbo:registry address="zookeeper://192.168.54.130:2181"/>
<!--配置服务注解扫描-->
<dubbo:annotation package="cn.khue.service.impl" />
<!--配置服务Bean-->
<bean id="userServiceImpl" class="cn.khue.service.impl.UserServiceImpl" />
</beans>
新建UserServiceImpl.java
import cn.khue.pojo.User;
import cn.khue.service.UserService;
import com.alibaba.dubbo.config.annotation.Reference;
import org.springframework.stereotype.Service;
@Service
public class UserServiceImpl implements UserService {
@Reference
private UserService userService;
@Override
public User login(User user) {
return userService.login(new User());
}
}
测试类
import org.junit.Test;
import cn.khue.pojo.User;
import cn.khue.service.UserService;
import cn.khue.service.impl.UserServiceImpl;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class AppTest {
@Test
public void test() {
//获取配置文件
ApplicationContext ac=new ClassPathXmlApplicationContext("dubbo-consumer-config.xml");
//获取bean
UserService userService = ac.getBean("userServiceImpl", UserServiceImpl.class);
//调用方法
User user = userService.login(new User());
//输出测试数据
System.out.println(user);
}
}
注意:使用注解式开发时,远程Linux需要关闭防火墙(service iptables stop)
6.Registry的可视化管理
1.在tomcat中部署dubbo-admin-2.5.10.war(部署后,关闭tomcat,删除war包后再重启-避免重启覆盖)
2.配置dubbo-admin-2.5.10项目下WEB-INF目录下的dubbo.properties
Assembly插件
由于服务提供方Provider不需要依赖web容器,所以可将其打成jar包
Nginx
Nginx是一个高性能的HTTP和反向代理服务器
1.CentOS6.5下安装
1.依赖安装
2.上传并解压nginx压缩包
3.配置检查
4.检查无误后编译安装
5.启动(nginx自动在/usr/local/下创建了nginx目录,nginx的启动文件在该目录的sbin目录下)
6.关闭防火墙后访问(默认端口为80)
vsftpd
vsftpd是一款遵循ftp协议的FTP服务器程序,小巧方便、安全易用
1.CentOS6.5下安装
1.新建用户
2.在线安装vsftpd
3.查看是否可用
4.开放权限
5.关闭匿名访问
6.指定访问端口范围
7.设置开启自启
8.重启服务
2.文件上传实现
1.新建web-app的maven项目
2.引入相关依赖(commons-fileupload、commons-io、commons-net、spring等)
3.配置web.xml
<?xml version="1.0" encoding="utf-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" id="WebApp_ID" version="3.0">
<!--配置监听器-->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!--配置spring配置文件路径-->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring-*.xml</param-value>
</context-param>
<!--配置springmvc的DispatcherServlet-->
<servlet>
<servlet-name>dispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring-mvc-config.xml</param-value>
<init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dispatcherServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<!--配置编码过滤器-->
<filter>
<filter-name>characterEncodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>utf-8</param-value>
</init-param>
<init-param>
<param-name>forceEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>characterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</web-app>
4.配置spring-mvc-config.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!--配置注解驱动-->
<mvc:annotation-driven/>
<!--配置组件扫描器-->
<context:component-scan base-package="cn.khue.controller"></context:component-scan>
<!--配置文件上传解析器-->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"></bean>
</beans>
5.配置spring-config.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!--配置组件扫描器-->
<context:component-scan base-package="cn.khue.service.impl"></context:component-scan>
<!--加载属性文件-->
<context:property-placeholder location="classpath:vsftpd.properties"></context:property-placeholder>
</beans>
6.配置vsftpd属性文件
vsftpd.host=192.168.54.130
vsftpd.port=21
vsftpd.username=ftpuser
vsftpd.password=ftpuser
vsftpd.basePath=/home/ftpuser
vsftpd.filePath=/
nginx.url=http://192.168.76.30/
7.创建文件上传页
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<c:set var="ctx" value="${pageContext.request.contextPath}"></c:set>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<body>
<form action="${ctx}/fileUpload.do" method="post" enctype="multipart/form-data">
<input type="file" name="img"><br/>
<input type="submit" value="上传">
</form>
</body>
</html>
8.创建测试代码
controller
import cn.khue.service.FileUploadService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.multipart.MultipartFile;
import java.util.Map;
@Controller
public class FileUploadController {
@Autowired
private FileUploadService fileUploadService;
@RequestMapping("/fileUpload.do")
public String fileUpload(MultipartFile img, Model model){
Map<String, Object> map = fileUploadService.fileUpload(img);
if(map.containsKey("url")){
model.addAttribute("url",map.get("url"));
return "/success.jsp";
}else{
return "/fail.jsp";
}
}
}
service
import org.springframework.web.multipart.MultipartFile;
import java.util.Map;
public interface FileUploadService {
//文件上传
Map<String,Object> fileUpload(MultipartFile img);
}
import cn.khue.service.FileUploadService;
import cn.khue.utils.FtpUtil;
import cn.khue.utils.IDUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
@Service
public class FileUploadServiceImpl implements FileUploadService {
@Value("${vsftpd.host}")
private String host;
@Value("${vsftpd.port}")
private int port;
@Value("${vsftpd.username}")
private String username;
@Value("${vsftpd.password}")
private String password;
@Value("${vsftpd.basePath}")
private String basePath;
@Value("${vsftpd.filePath}")
private String filePath;
@Value("${nginx.url}")
private String nginxUrl;
@Override
public Map<String,Object> fileUpload(MultipartFile img) {
Map<String,Object> map = new HashMap<>();
try {
String fileName = IDUtils.genImageName()+img.getOriginalFilename().substring(img.getOriginalFilename().lastIndexOf("."));
boolean result = FtpUtil.uploadFile(host, port, username, password, basePath, filePath, fileName, img.getInputStream());
if(result){
map.put("url",nginxUrl+fileName);
}
} catch (IOException e) {
e.printStackTrace();
}
return map;
}
}
util
import org.apache.commons.net.ftp.FTP;
import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.net.ftp.FTPFile;
import org.apache.commons.net.ftp.FTPReply;
import java.io.*;
/**
* ftp上传下载工具类
*/
public class FtpUtil {
/**
* Description: 向FTP服务器上传文件
* @param host FTP服务器hostname
* @param port FTP服务器端口
* @param username FTP登录账号
* @param password FTP登录密码
* @param basePath FTP服务器基础目录
* @param filePath FTP服务器文件存放路径。例如分日期存放:/2015/01/01。文件的路径为basePath+filePath
* @param filename 上传到FTP服务器上的文件名
* @param input 输入流
* @return 成功返回true,否则返回false
*/
public static boolean uploadFile(String host, int port, String username, String password, String basePath,
String filePath, String filename, InputStream input) {
boolean result = false;
FTPClient ftp = new FTPClient();
try {
int reply;
ftp.connect(host, port);// 连接FTP服务器
// 如果采用默认端口,可以使用ftp.connect(host)的方式直接连接FTP服务器
ftp.login(username, password);// 登录
reply = ftp.getReplyCode();
if (!FTPReply.isPositiveCompletion(reply)) {
ftp.disconnect();
return result;
}
//切换到上传目录
if (!ftp.changeWorkingDirectory(basePath+filePath)) {
//如果目录不存在创建目录
String[] dirs = filePath.split("/");
String tempPath = basePath;
for (String dir : dirs) {
if (null == dir || "".equals(dir)) continue;
tempPath += "/" + dir;
if (!ftp.changeWorkingDirectory(tempPath)) {
if (!ftp.makeDirectory(tempPath)) {
return result;
} else {
ftp.changeWorkingDirectory(tempPath);
}
}
}
}
//设置上传文件的类型为二进制类型
ftp.setFileType(FTP.BINARY_FILE_TYPE);
//上传文件
if (!ftp.storeFile(filename, input)) {
return result;
}
input.close();
ftp.logout();
result = true;
} catch (IOException e) {
e.printStackTrace();
} finally {
if (ftp.isConnected()) {
try {
ftp.disconnect();
} catch (IOException ioe) {
}
}
}
return result;
}
/**
* Description: 从FTP服务器下载文件
* @param host FTP服务器hostname
* @param port FTP服务器端口
* @param username FTP登录账号
* @param password FTP登录密码
* @param remotePath FTP服务器上的相对路径
* @param fileName 要下载的文件名
* @param localPath 下载后保存到本地的路径
* @return
*/
public static boolean downloadFile(String host, int port, String username, String password, String remotePath,
String fileName, String localPath) {
boolean result = false;
FTPClient ftp = new FTPClient();
try {
int reply;
ftp.connect(host, port);
// 如果采用默认端口,可以使用ftp.connect(host)的方式直接连接FTP服务器
ftp.login(username, password);// 登录
reply = ftp.getReplyCode();
if (!FTPReply.isPositiveCompletion(reply)) {
ftp.disconnect();
return result;
}
ftp.changeWorkingDirectory(remotePath);// 转移到FTP服务器目录
FTPFile[] fs = ftp.listFiles();
for (FTPFile ff : fs) {
if (ff.getName().equals(fileName)) {
File localFile = new File(localPath + "/" + ff.getName());
OutputStream is = new FileOutputStream(localFile);
ftp.retrieveFile(ff.getName(), is);
is.close();
}
}
ftp.logout();
result = true;
} catch (IOException e) {
e.printStackTrace();
} finally {
if (ftp.isConnected()) {
try {
ftp.disconnect();
} catch (IOException ioe) {
}
}
}
return result;
}
public static void main(String[] args) {
try {
FileInputStream in=new FileInputStream(new File("E:/a.png"));
boolean flag = uploadFile("192.168.17.129", 21, "ftpuser", "ftpuser", "/home/ftpuser/","/", "abc1.png", in);
System.out.println(flag);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
}
import java.util.Random;
/**
* 各种id生成策略
* @version 1.0
*/
public class IDUtils {
/**
* 图片名生成
*/
public static String genImageName() {
//取当前时间的长整形值包含毫秒
long millis = System.currentTimeMillis();
//long millis = System.nanoTime();
//加上三位随机数
Random random = new Random();
int end3 = random.nextInt(999);
//如果不足三位前面补0
String str = millis + String.format("%03d", end3);
return str;
}
/**
* 商品id生成
*/
public static long genItemId() {
//取当前时间的长整形值包含毫秒
long millis = System.currentTimeMillis();
//long millis = System.nanoTime();
//加上两位随机数
Random random = new Random();
int end2 = random.nextInt(99);
//如果不足两位前面补0
String str = millis + String.format("%02d", end2);
long id = new Long(str);
return id;
}
public static void main(String[] args) {
for(int i=0;i< 100;i++)
System.out.println(genItemId());
}
}
Redis
Redis是基于C的一个高性能键值对存储的非关系型数据库
1.单机版安装(centos6.5)
1.安装c环境
2.上传安装包并解压
3.编译安装
4.开启守护线程
5.拷贝redis.conf至bin目录
6.启动
6.进入redis
7.关闭
2.集群版安装(centos6.5)
redis集群中至少应该有3个节点,每个节点至少有一个备份节点
1.创建redis-cluster目录(用于防止集群的节点)
2.复制一个redis主机至redis-cluster(取名为7001)
3.创建日志目录
4.替换7001的redis.conf文件
vim下使用gg回车后,在使用dG快速清空文件
4.复制7001为7002、7003、7004、7005、7006并修改对应配置文件
如果需要开启aof持久化则还需要添加如下两条信息
# 开启aof持久化
appendnoly yes
appendfsync always
5.批量启动
编写批量启动的批处理文件
查看授权
授权
启动
6.查看
7.配置主从关系
进入任意700x下执行组合命令
8.连接集群
查看集群信息
9.关闭集群
编写批量关闭的批处理文件
授权
关闭
3.redis的相关配置
redis的相关配置主要存在与redis.conf文件
1.绑定IP
一般将IP绑定为0.0.0.0,这样方便远程访问
绑定指定IP后,进入redis时,需要使用-h ip参数(比如将ip绑定为192.168.54.130后,不使用-h ip 不能进入redis)
2.端口号
3.数据库数量
4.持久化
redis默认使用rdb文件实现持久化
redis也支持aof持久化(修改appendonly=yes即可)
5.密码验证
取消requirepass的注解,将foobared修改为密码即可实现登录密码验证
4.数据类型
key:字符串
value:常见的(String、hash、list、sorted set/zset)
5.常用命令
1.key相关命令
1.查询
# 查询所有key
keys *
# 查看所有包含t的key
keys *t*
# 查看长度为3的key
keys ???
# 判断k是否存在
exists key01
# 获取k的类型
type key01
2.删除
# 删除单个k
del key01
# 删除多个k
del key01 key02
3.过期时间
# 设置k的过期时间(单位:S)
expire key01 30
# 查看k的剩余时间
# 没有设置则返回-1
# 已过期返回-2
ttl key01
2.String类型常用命令
1.增加/修改
# 有key则修改
# 无key则增加
set key value
# 返回原值并附新值
getset key newValue
2.查询
get key
3.删除
del key
4.自增、自减
# 自增
# 如果key不存在,其初始值为0,incr之后为1
# 如果value不能转为整数数字,则操作失败并返回错误信息
incr key
# 指定自增数
incrby key n
# 自减
# 如果key不存在,其初始值为0,decr之后为-1
decr key
# 指定自减数
decrby key n
5.拼接字符串
# key存在就在原值后追加
# key不存在就新建
append key xx
3.Hash类型常用命令
1.赋值
# 单个kv
hset name key value
# 多个kv
hset name key01 value01 key02 value02
2.取值
# 返回单个v
hget name key
# 返回多个v
hmget name key01 key02
# 返回多个k
hgettall name
3.删除
# 删除多个k
del name
# 删除单个k
del name key01
6.redis-desktop-manager
下载安装redis-desktop-manager可对redis进行可视化操作
7.集群的存储原理
redis集群中内置16384(0~16383)个槽,redis会根据节点数量大致均等的将16384个槽映射到不同的节点。存储一个kv时,redis先对k进行crc16算法运算一个结果,然后把结果对16384取余
8.Jedis操作
1.新建一个quick-start的maven项目
2.引入redis.clients的jedis和commons-pool的commons-pool的依赖
3.编写测试代码
public class AppTest{
@Test
public void test(){
//连接Jedis单机版
Jedis jedis=new Jedis("192.168.54.130",6379);
//新增
jedis.set("name","Khue");
//删除
jedis.del("name");
//修改
jedis.set("name","赵大土");
//查询
String name=jedis.get("name");
}
}
9.JedisPool操作
1.新建一个quick-start的maven项目
2.引入redis.clients的jedis和commons-pool的commons-pool的依赖
3.编写测试代码
public class AppTest{
@Test
public void test(){
//创建连接池配置对象
JedisPoolConfig config=new JedisPoolConfig();
//设置最大连接数
config.setMaxTotal(60);
//创建连接池对象
JedisPool pool =new JedisPool(config,"192.168.54.130",6379);
//获取redis对象
Jedis jedis=pool.getResource();
//数据操作
jedis.hset("user","name","khue");
jedis.hget("user","name");
...
}
}
10.JedisCluster操作
1.新建一个quick-start的maven项目
2.引入redis.clients的jedis和commons-pool的commons-pool的依赖
3.编写测试代码
public class AppTest{
@Test
public void test(){
//创建redis集群主机和端口信息
HashSet<HostAndPort> set=new HashSet<>();
set.add(new HostAndPort("192.168.54.130",7001));
set.add(new HostAndPort("192.168.54.130",7002));
set.add(new HostAndPort("192.168.54.130",7003));
set.add(new HostAndPort("192.168.54.130",7004));
set.add(new HostAndPort("192.168.54.130",7005));
set.add(new HostAndPort("192.168.54.130",7006));
//创建JedisCluster对象
JedisCluster cluster=new JedisCluster(set);
//数据操作
jedisCluster.set("name","khue");
...
}
}
10.Jedis整合Spring
1.导入redis.clients的jedis、commons-pool的commons-pool、org.springframework的spring-context、org.springframework的spring-tx的资源
2.在spring的配置文件中配置jedis
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context" xmlns:p="http://www.springframework.org/schema/p"
xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.0.xsd">
<!-- 配置jedis连接池 -->
<bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig">
<!-- 最大连接数 -->
<property name="maxTotal" value="30" />
<!-- 最大空闲连接数 -->
<property name="maxIdle" value="10" />
<!-- 每次释放连接的最大数目 -->
<property name="numTestsPerEvictionRun" value="1024" />
<!-- 释放连接的扫描间隔(毫秒) -->
<property name="timeBetweenEvictionRunsMillis" value="30000" />
<!-- 连接最小空闲时间 -->
<property name="minEvictableIdleTimeMillis" value="1800000" />
<!-- 连接空闲多久后释放, 当空闲时间>该值 且 空闲连接>最大空闲连接数 时直接释放 -->
<property name="softMinEvictableIdleTimeMillis" value="10000" />
<!-- 获取连接时的最大等待毫秒数,小于零:阻塞不确定的时间,默认-1 -->
<property name="maxWaitMillis" value="1500" />
<!-- 在获取连接的时候检查有效性, 默认false -->
<property name="testOnBorrow" value="true" />
<!-- 在空闲时检查有效性, 默认false -->
<property name="testWhileIdle" value="true" />
<!-- 连接耗尽时是否阻塞, false报异常,ture阻塞直到超时, 默认true -->
<property name="blockWhenExhausted" value="false" />
</bean>
<!-- jedis整合spring单机版 -->
<bean id="jedisPool" class="redis.clients.jedis.JedisPool">
<!-- 属性注入方式不可行,因为该类中没有相应的属性 -->
<!--
<property name="host" value="192.168.76.40"/>
<property name="port" value="6379"/>
<property name="poolConfig" ref="jedisPoolConfig"/>
-->
<!-- 可通过构造注入方式赋值,因为该类中有相应的构造方法 -->
<constructor-arg name="host" value="192.168.54.130"/>
<constructor-arg name="port" value="6379"/>
<constructor-arg name="poolConfig" ref="jedisPoolConfig"/>
</bean>
<!-- jedisCluster集群 -->
<bean id="jedisCluster" class="redis.clients.jedis.JedisCluster">
<constructor-arg name="poolConfig" ref="jedisPoolConfig" />
<constructor-arg name="nodes">
<set>
<bean class="redis.clients.jedis.HostAndPort">
<constructor-arg name="host" value="192.168.54.130" />
<constructor-arg name="port" value="7001" />
</bean>
<bean class="redis.clients.jedis.HostAndPort">
<constructor-arg name="host" value="192.168.54.130" />
<constructor-arg name="port" value="7002" />
</bean>
<bean class="redis.clients.jedis.HostAndPort">
<constructor-arg name="host" value="192.168.54.130" />
<constructor-arg name="port" value="7003" />
</bean>
<bean class="redis.clients.jedis.HostAndPort">
<constructor-arg name="host" value="192.168.54.130" />
<constructor-arg name="port" value="7004" />
</bean>
<bean class="redis.clients.jedis.HostAndPort">
<constructor-arg name="host" value="192.168.54.130" />
<constructor-arg name="port" value="7005" />
</bean>
<bean class="redis.clients.jedis.HostAndPort">
<constructor-arg name="host" value="192.168.54.130" />
<constructor-arg name="port" value="7006" />
</bean>
</set>
</constructor-arg>
</bean>
</beans>
3.编写测试代码
public class AppTest{
@Test
public void test(){
//读取配置文件
ApplicationContext ac=new ClassPathXmlApplication("spring-config.xml");
JedisCluster cluster=ac.getBean("jeddisCluster",JedisCluster.class);
cluster.set("name","khue");
String name=cluster.get("name");
}
}
JSONP
1.同源策略
为了保证用户信息安全,防止恶意的网址窃取数据,浏览器仅允许某个网页脚本访问与该网页脚本同源(协议、域名、端口都相同)的另一个网页的数据;如果不同源,则ajax请求不能发送、dom无法获取、cookie、local storage和indexdb无法读取等
2.ajax跨源策略
1.jsonp【json with padding】
因为src属性没有同源限制(浏览器解析含有src属性的标签时,会自动下载src属性值指向的资源),jsonp通过动态创建script标签,指定其src属性为跨域的api,服务器接收请求处理后返回回调函数
<script>
$(function(){
$("button").click(function(){
$.ajax(){
url:"http://localhost:8081/jsonp/test",
dataType:"jsonp",
jsonp:"callback",//设置回调函数名
success:function(data){
alert(data);
}
}
})
})
</script>
import com.fasterxml.jackson.databind.util.JSONPObject;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class TestController {
@RequestMapping("/test")
public Object test(String callback){
String result="ok";
return new JSONPObject(callback,result);
}
}
json是一种基于文本的数据交互方式,jsonp是一种非官方的跨域数据交互协议
由于使用的是src属性,所以仅支持get方法
2.websocket:websocket是一种通信协议,为了解决HTTP协议的弊端而产生(HTTP协议中,通信只能由客户端发起)。websocket实现了双向对话,而且没有同源限制,使用ws://或wss://作为前缀
3.cors:cors【cross-origin resource sharing】是一个W3C标准,实现cors需要浏览器和服务器同时支持(目前几乎所有浏览器都支持,有些老旧浏览器可能不支持),cors通信过程是浏览器自动完成的,无需用户参与,浏览器一旦发现ajax请求跨域,就会自动附加一些头信息(有时会多发一次附加请求),所以开发者只需在服务器实现cors接口即可
<script>
$(function(){
$("button").click(function(){
$.ajax(){
url:"http://localhost:8081/jsonp/test",
success:function(data){
alert(data);
}
}
})
})
</script>
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class TestController {
@CrossOrigin
@RequestMapping("/test")
public String test(String callback){
return "ok";
}
}
Solr
solr是要给基于lucene的Java搜索引擎服务器
1.基本概念
1.document
document是solr交互的基本单元,solr将一个document中的数据作为一个整体进行处理
<!--xml格式-->
<doc>
<field name="id">01</field>
<field name="title">this is a test</field>
</doc>
<!--json格式-->
"doc":{"id":"01","title":"this is a test"}
2.field
field是document的数据单元,solr处理的数据都是存放在field中(如果document是Java类,则field是成员属性)
field在schema.xml中配置
field可分为定义完全的域(field)、复制域(copyField)、dynamicField(动态域)等
<!--field的一般格式-->
<field name="price" type="float" default="0.0" indexed="true" stored="true" />
<!--指定field的type属性-->
<fieldType name="float" class="solr.TrieFloatField" precisionStep="0" positionIncrementGap="0" />
3.倒排索引
倒排索引也叫反向索引,一般正向索引是扫描索引库的所有文档,找出所有包含关键词的文档,然后根据打分模型进行打分排序,而反向索引是将正向索引重新构建,将文件ID对应到关键词的映射转换为关键词到文件ID的映射
2.centos6.5下单机版安装
1.安装jdk(省略)
2.安装tomcat(省略)
3.上传solr安装包并解压
4.将解压后的目录中dist目录下的solr-4.10.3.war部署到tomcat(复制war,后启动tomcat,启动成功后再关闭tomcat,后删除war)
5.复制solr安装包中example目录下lib目录下ext目录下所有jar到tomcat中solr项目的WEB-INF下lib目录中
6.修改tomcat下solr项目的web.xml(配置solrhome的位置)
7.启动tomcat访问solr
3.solrj的使用
1.引入solrj的依赖
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.solr</groupId>
<artifactId>solr-solrj</artifactId>
<version>4.10.3</version>
</dependency>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>
</dependencies>
2.编写测试代码
import org.apache.solr.client.solrj.SolrServerException;
import org.apache.solr.client.solrj.impl.HttpSolrServer;
import org.apache.solr.common.SolrInputDocument;
import org.junit.Test;
import java.io.IOException;
public class AppTest {
@Test
public void test() throws IOException, SolrServerException {
//连接
HttpSolrServer solrServer=new HttpSolrServer("http://192.168.54.130:8080/solr-4.10.3");
//新增doc
SolrInputDocument document=new SolrInputDocument();
document.setField("id","1");
document.setField("title","test");
solrServer.add(document);
//根据id删除doc
solrServer.deleteById("1");
//删除所有doc
solrServer.deleteByQuery("*:*");
//查询所有
SolrQuery solrQuery=new SolerQuery("*:*");
//查询title为手机
SolrQuery solrQuery=new SolerQuery("title:手机");
//根据id降序
solrQuery.setQuery("id",SolrQuery.ORDER.desc);
//分页
solrQuery.setStart(1);
solrQuery.setRows(3);
//开启高亮
solrQuery.setHighlight(true);
//设置高亮字段
solrQuery.addHightlightField("title");
//添加前缀
solrQuery.setHighlightSimplePre("<span style='color:red'>");
//添加后缀
solrQuery.setHighlightSimplePost("</span>");
//查询结果
SolrResponse solrResponse=solrServer.query(solrQuery);
//获取高亮内容
Map<String,Map<Stirng,List<String>>> highlighting=queryResponse.getHighlighting();
System.out.println(highlighting);
//获取所有doc
SolrDocumentList documentList=solrResponse.getResults();
for(SolrDocument document:documentList){
System.out.println(document);
}
//提交
solrServer.commit();
}
}
3.查询结果
4.IKAnalyzer
solr默认情况下不支持中文分词,可通过第三方插件来实现中文分词
1.上传文件IK目录
2.将IK依赖的jar放入solr项目的WEB-INF/lib
3.将IK需要的扩展词典、停用词词典、配置文件复制到solr项目的类路径下(没有就自己再WEB-INF下创建classes目录)
4.在solrhome的collection1/conf/schema.xml中配置filed及fieldType
5.整合Spring
1.依赖
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.solr</groupId>
<artifactId>solr-solrj</artifactId>
<version>4.10.3</version>
</dependency>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.1.RELEASE</version>
</dependency>
</dependencies>
2.spring配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<bean id="httpSolrServer" class="org.apache.solr.client.solrj.impl.HttpSolrServer">
<constructor-arg name="baseURL" value="http://192.168.54.130:8080/solr-4.10.3"/>
</bean>
</beans>
3.测试代码
@Test
public void test01() throws IOException, SolrServerException {
ApplicationContext ac=new ClassPathXmlApplicationContext("spring-config.xml");
HttpSolrServer httpSolrServer = ac.getBean("httpSolrServer", HttpSolrServer.class);
//新增数据
SolrInputDocument document=new SolrInputDocument();
document.setField("id","2");
document.setField("title","test02");
httpSolrServer.add(document);
//提交
httpSolrServer.commit();
}
6.SolrCloud
SolrCloud是一个基于Solr和Zookeeper(作为集群的配置信息管理中心)的分布式搜索方案
特色功能:集中式的配置信息、自动容错、近实时搜索、查询时自动负载均衡
SolrCloud为了降低单机的处理压力,需要多台服务器共同完成索引和搜索任务,其实现思路是将索引数据进行分片拆分(shard),每个分片由多台服务器共同完成,当一个索引或搜索请求过来时,会分别从不同的分片的服务器中操作索引。
1个collection至少分成2个shard,每个shard至少由2个core组成,其中1个leader、1个replication,zookeeper负责选举leader(选举可以发生在任何时间,但一般只在某个solr发生故障才会触发,选举策略一般是先到先得)和控制同一个shard的core的数据一致,一般1个solr包含2个core,而3个solr组成一个SolrCloud
1.centos6.5下安装
【安装zookeeper集群】
1.上传zookeeper安装包并解压重命名为zookeeper01
2.在zookeeper01根目录下新建data目录
3.在data目录下新建文件myid
4.复制zookeeper01/conf下的zoo_sample.cfg,并改名为zoo.cfg
5.修改zoo.cfg
6.复制zookeeper01两份,分别为zookeeper02、zookeeper03
7.修改zookeeper02和zookeeper03的myid、dataDIR和clientPort
8.编写三个zookeepe同时启动的脚本
SolrCloud
1.复制3个tomcat
Spring Cloud
springcloud是一个服务治理平台,提供了一些服务框架(服务注册与发现、配置中心、消息中心、负载均衡、断路器、数据监控等),它将目前比较成熟的服务框架组合起来,通过springboot风格进行再封装,屏蔽掉了复杂的配置和实现原理,形成了一套易部署、易维护的分布式系统开发工具包,简而言之,springcloud为开发者提供了快速构建分布式系统的工具,开发者可以快速启动服务或构建应用,同时能够快速和云平台资源进行对接
springcloud相比RPC/SOA框架而言,提供了全套的分布式系统解决方案(配置管理、服务治理、熔断机制、智能路由、微代理、控制总线、一次性token、全局一致性锁、leader选举、分布式session、集群状态管理等)
1.版本号说明
开发中,常见版本号格式是x.y.z.stage
x:主版本号(功能模块有较大更新或整体架构发生变化)
y:次版本号(局部更新)
z:修公版本号(bug修复或小变动)
stage:阶段版本号(标注当前版本处于哪个开发阶段:BASE-设计阶段【只有设计没有具体功能实现】、ALPHA-初级阶段【存在较多bug】、BATE-进阶阶段【潜在bug】、RELEASE/FINAL-正式发布阶段)
由于springcloud包含了若干子框架,使用场景版本号进行标记容易混淆主框架和子框架版本,于是使用了一种全新的版本号来标记主框架,子框架大多还是使用常用版本号进行标记
springcloud版本格式是x.stage
x:版本号使用英国伦敦地铁站名称进行标记,并按首字母自然升序排列(Angle、Brixton、Camden等)
stage:阶段版本号,常用BUILD-XXX[SNAPSHOT] (开发版本,常用于内部)、GA(稳定版本-存在未测出的bug隐患,不推荐商用)、PRE(里程碑版【milestone】-修复了一些bug,常有多个版本M1、M2…不推荐商用)、RC(候选发布版-修复高等级bug,存在多个版本RC1、RC2…)、SR(正式发布版-优化或修复了大bug,存在多个版本SR1、SR2…)
2.Spring Cloud Netflix
SpringCloudNetflix是针对Netflix组件提供的开发工具包
1.Eureka - Service Registry/Discovery
eureka是一个基于rest服务的服务治理组件,包括服务注册中心、服务注册与服务发现机制的实现、实现了云端负载均衡和中间层服务器的故障转移,包括Eureka Server和Eureka Client两个组件
Eureka Server:提供服务注册的服务,通过Register、Get、Renew等接口提供服务的注册、发现和心跳检测等
Eureka Client:用于简化与Eureka Server交互的Java客户端,具备一个内置的、使用轮询负载算法的负载均衡器,应用启动后会想Eureka Server发送默认周期30s的心跳,Eureka Server默认在90s内没有接收到该节点的心跳,则会将其从服务注册表中移除。Eureka Client分为Application Service(service provider)和Application Client(service consumer)两个角色
服务过程:
1.Eureka Client通过register将自己的IP和端口注册到Eureka Server,然后通过renew发送心跳包告知Eureka Server自己还存活
2.Eureka Client通过get registry向Eureka Server获取服务列表,匹配对于服务后,通过make remote call完成服务的远程调用
3.Eureka Client通过cancel向Eureka Server发送消息,删除服务列表中对于的服务
4.Eureka Server通过replicate向其他Eureka Server完成数据复制与同步
1.单机版
1.引入依赖
<dependencyManagement>
<dependencies>
<!--管理spring boot资源-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.1.10.RELEASE</version>
<type>pom</type>
<!--只引入资源-->
<scope>import</scope>
</dependency>
<!--管理spring cloud资源-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Greenwich.SR4</version>
<type>pom</type>
<!--只引入资源-->
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<!--web资源-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.2.1.RELEASE</version>
</dependency>
<!--eureka server资源-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
</dependencies>
2.编写配置
# tomcat端口(保持与eureka端口一致)
server.port=8761
# 服务名称
spring.application.name=eureka-server01
3.编写启动类
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
@SpringBootApplication
@EnableEurekaServer
public class EurekaTest {
public static void main(String[] args) {
SpringApplication.run(EurekaTest.class,args);
}
}
2.集群版
Eureka集群版中每个节点都是平等的,所有节点同时对外提供服务的发现、注册等功能,由于每个Eureka Server也是一个服务,所以每个Eureka Server之间可以相互注册,且可以发现其他Eureka Server中已经注册的服务
1.配置第一台
# 服务端口
server.port=8761
# 服务名称
spring.application.name=eureka-server01
# 注册中心地址
eureka.client.service-url.defaultZone=http://192.168.54.130:8761/erueka/
2.配置第二台
# 服务端口
server.port=8761
# 服务名称
spring.application.name=eureka-server02
# 注册中心地址
eureka.client.service-url.defaultZone=http://192.168.54.131:8761/erueka/
3.服务实例
1.注册中心
依赖
<dependencyManagement>
<dependencies>
<!--管理spring boot资源-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.1.10.RELEASE</version>
<type>pom</type>
<!--只引入资源-->
<scope>import</scope>
</dependency>
<!--管理spring cloud资源-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Greenwich.SR4</version>
<type>pom</type>
<!--只引入资源-->
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<!--web资源-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.2.1.RELEASE</version>
</dependency>
<!--eureka server资源-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
</dependencies>
配置
# tomcat端口(保持与eureka端口一致)
server.port=8761
# 服务名称
spring.application.name=eureka-server01
启动类
@SpringBootApplication
@EnableEurekaServer
public class App {
public static void main(String[] args) {
SpringApplication.run(App.class,args);
}
}
2.服务提供者
依赖
<dependencyManagement>
<dependencies>
<!--管理spring boot资源-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.1.10.RELEASE</version>
<type>pom</type>
<!--只引入资源-->
<scope>import</scope>
</dependency>
<!--管理spring cloud资源-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Greenwich.SR4</version>
<type>pom</type>
<!--只引入资源-->
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<!--web资源-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--eureka client资源-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
</dependencies>
配置
# tomcat端口
server.port=8081
# 服务名称
spring.application.name=eureka-service-provider01
# 注册中心
eureka.client.service-url.defaultZone=http://localhost:8761/eureka/
代码
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class TestController {
@RequestMapping("/test")
public String test(){
return "test";
}
}
启动类
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
@SpringBootApplication
@EnableEurekaClient
public class App {
public static void main(String[] args) {
SpringApplication.run(App.class,args);
}
}
3.服务消费者
依赖
<dependencyManagement>
<dependencies>
<!--管理spring boot资源-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.1.10.RELEASE</version>
<type>pom</type>
<!--只引入资源-->
<scope>import</scope>
</dependency>
<!--管理spring cloud资源-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Greenwich.SR4</version>
<type>pom</type>
<!--只引入资源-->
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<!--web资源-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--eureka client资源-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!--thymeleaf资源-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
</dependencies>
配置
# tomcat端口
server.port=8082
# 服务名称
spring.application.name=eureka-service-consumer01
# 注册中心
eureka.client.service-url.defaultZone=http://localhost:8761/eureka/
代码
import cn.khue.service.ConsumerService;
import org.springframework.stereotype.Controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
public class ConsumerController {
@Autowired
private ConsumerService consumerService;
@RequestMapping("/test")
public String test(){
return consumerService.test();
}
}
public interface ConsumerService {
String test();
}
import cn.khue.service.ConsumerService;
import org.springframework.http.HttpMethod;
import org.springframework.stereotype.Service;
import org.springframework.http.ResponseEntity;
import org.springframework.web.client.RestTemplate;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
@Service
public class ConsumerServiceImpl implements ConsumerService {
//负载均衡客户端,与Eureka Server互通
@Autowired
private LoadBalancerClient loadBalancerClient;
@Override
public String test() {
//通过服务名称查找服务实例
ServiceInstance serviceInstance = loadBalancerClient.choose("eureka-service-provider01");
//准备远程服务的url
String url = "http://" +
serviceInstance.getHost() +
":" +
serviceInstance.getPort() +
"/test";
//发起http请求
RestTemplate restTemplate=new RestTemplate();
//类型解析器(此处响应结果类型为String)
ParameterizedTypeReference<String> type=new ParameterizedTypeReference<String>() {};
//需要提供访问的url、请求方式、请求体及响应结果的类型解析器
ResponseEntity<String> result = restTemplate.exchange(url, HttpMethod.GET, null, type);
return result.getBody();
}
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>this is consumer</h1>
</body>
</html>
启动类
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
@SpringBootApplication
@EnableEurekaClient
public class App {
public static void main(String[] args) {
SpringApplication.run(App.class,args);
}
}
注意:启动类中的@EnableEurekaClient在G版本SpringCloud中可省略
2.Ribbon - Client Side Load Banlancer
ribbon是一个基于http和tcp的客服端负载均衡工具,它提供多种负载均衡算法,甚至可以自定义算法
业界主流的两种负载均衡解决方案:
1.集中式负载均衡:即在客户端和服务端之间使用独立的负载均衡设施(硬件如F5,软件如nginx),然后由该设施负责把访问请求通过某种策略转发至服务端
2.进程内负载均衡:即将负载均衡逻辑集成在客户端组件中,客户端组件从服务注册中心获取可用地址,然后再从地址中选择一个合适的服务端发起请求(软件如ribbon)
1.常用的负载均衡策略
1.轮询策略(默认)-RoundRobinRule:按顺序循环选取
2.权重轮询策略-WeightedResponseTimeRule:根据响应时间分配权重,响应时间越长,权重越小,选中的可能性越小
3.随机策略(不推荐)-RandomRule:随机选取
4.最少并发数策略-BestAvailableRule:选取此刻并发数最小的(正在熔断除外)
5.重试策略-RetryRule:在预设时间内所选中的无响应,则使用轮询策略选择可用的最后一个
6.可用性敏感策略-AvailabilityFilteringRule:过滤掉连接失败和高并发的
7.区域性敏感性策略-ZoneAvoidanceRule:从一个IP区域中选取可用的,如果该IP区域一个或多个不可用或响应变慢,则会降低该区域内的权重
2.指定策略的方式
在服务消费者的配置文件中配置
# eureka-application-service为需要调用的服务的名称
eureka-application-service.ribbon.NFLoadBalancerRuleClassName=com.netflix.loadbalancer.WeightedResponseTimeRule
3.Feign - REST Client
feign是一个基于ribbon和hystrix的声明式、模板化的服务调用组件(仅在服务消费方中使用),使用feign可用让服务调用向dubbo一样,服务消费方直接通过接口方法调用服务提供方,而无需通过常规的RestTemplate构造请求再解析返回数据
1.声明式远程服务调用实例
1.注册中心
依赖
<dependencyManagement>
<dependencies>
<!--管理spring boot资源-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.1.10.RELEASE</version>
<type>pom</type>
<!--只引入资源-->
<scope>import</scope>
</dependency>
<!--管理spring cloud资源-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Greenwich.SR4</version>
<type>pom</type>
<!--只引入资源-->
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<!--web资源-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.2.1.RELEASE</version>
</dependency>
<!--eureka server资源-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
</dependencies>
配置
# tomcat端口(保持与eureka端口一致)
server.port=8761
# 服务名称
spring.application.name=eureka-server-registry01
启动类
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
@SpringBootApplication
@EnableEurekaServer
public class App {
public static void main(String[] args) {
SpringApplication.run(App.class,args);
}
}
2.远程服务接口
依赖
<dependencyManagement>
<dependencies>
<!--管理spring boot资源-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.1.10.RELEASE</version>
<type>pom</type>
<!--只引入资源-->
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<!--web资源-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
接口
import org.springframework.web.bind.annotation.RequestMapping;
public interface RemoteService {
@RequestMapping("/test")
String test();
}
3.服务提供方
依赖
<dependencyManagement>
<dependencies>
<!--管理spring boot资源-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.1.10.RELEASE</version>
<type>pom</type>
<!--只引入资源-->
<scope>import</scope>
</dependency>
<!--管理spring cloud资源-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Greenwich.SR4</version>
<type>pom</type>
<!--只引入资源-->
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<!--web资源-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--eureka server资源-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!--远程服务接口资源-->
<dependency>
<groupId>cn.khue</groupId>
<artifactId>remote-service</artifactId>
<version>1.0-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
</dependencies>
配置
# tomcat端口
server.port=8081
# 服务名称
spring.application.name=eureka-service-provider01
# 注册中心
eureka.client.service-url.defaultZone=http://localhost:8761/eureka/
控制类
import cn.khue.service.TestService;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class TestController implements RemoteService {
@RequestMapping("/test")
public String test(){
return "test";
}
}
启动类
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
@SpringBootApplication
@EnableEurekaClient
public class App {
public static void main(String[] args) {
SpringApplication.run(App.class,args);
}
}
4.服务消费方
依赖
<dependencyManagement>
<dependencies>
<!--管理spring boot资源-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.1.10.RELEASE</version>
<type>pom</type>
<!--只引入资源-->
<scope>import</scope>
</dependency>
<!--管理spring cloud资源-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Greenwich.SR4</version>
<type>pom</type>
<!--只引入资源-->
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<!--web资源-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--eureka server资源-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!--feign资源-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!--thymeleaf资源-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!--远程服务接口资源-->
<dependency>
<groupId>cn.khue</groupId>
<artifactId>remote-service</artifactId>
<version>1.0-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
</dependencies>
配置
# tomcat端口
server.port=8082
# 服务名称
spring.application.name=eureka-service-consumer01
# 注册中心
eureka.client.service-url.defaultZone=http://localhost:8761/eureka/
控制类
import cn.khue.service.ConsumerService;
import org.springframework.stereotype.Controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
public class ConsumerController {
@Autowired
private ConsumerService consumerService;
@RequestMapping("/test")
public String test(){
return consumerService.test();
}
}
接口
import org.springframework.cloud.openfeign.FeignClient;
@FeignClient("eureka-service-provider01")
public interface ConsumerService extends RemoteService {
}
启动类
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
@SpringBootApplication
@EnableEurekaClient
@EnableFeignClients
public class App {
public static void main(String[] args) {
SpringApplication.run(App.class,args);
}
}
注意:
1.一般将服务接口单独分离成一个模块
2.服务接口中get方式传递非自定义参数必须使用@RequestParam(无论方法参数名是否和请求参数名一致),get方式无法传递自定义参数
3.服务接口中post方式传递单个非自定义参数必须使用@RequestBody(使用请求体json格式),传递多个非自定义参数必须使用@RequestParam(使用请求头),传递自定义类型参数必须使用@RequestBody
3.服务消费方中也可以省略服务接口的实现,可直接注入远程服务接口
import org.springframework.stereotype.Controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
public class ConsumerController {
@Autowired
private RemoteService remoteService;
@RequestMapping("/test")
public String test(){
return remoteService.test();
}
}
2.使用GZIP优化
gzip是一个采用deflate算法压缩数据的压缩格式,使用gzip压缩网络数据,可降低网络传输的字节数,提升网页加载速度,节省流量,改善用户体验,利于搜索引擎快速检索网页
实现:
1.客户端的请求头中带有Accept-Encoding:gzip,deflate字段
2.服务端接收请求,验证字段后,就对响应报文压缩,后响应带有Content-Encoding:gzip字段的响应头给客户端
3.客户端接收响应,验证响应头,开始解压报文
在服务消费方中配置Feign的gzip(只在feign请求/应答中开启)
# 开启请求gzip
feign.compression.request.enabled=true
# 开启响应gzip
feign.compression.response.enabled=true
# 配置支持gzip压缩的mime类型
feign.compression.request.mime-type=text/html,application/xml,application/json
# 配置压缩数据的最小阈值(默认2048字节)
feign.compression.request.min-request-size=512
在服务消费方中配置全局gzip(所有请求/应答都开启)
# 开启spring boot gzip
server.compression.enabled=true
# 配置支持gzip压缩的mime类型
server.compression.request.mime-type=text/html,application/xml,application/json
3.请求超时处理
请求时间是指客户端发起请求到服务端响应并成功建立双向链接的时间
链接时间是指双向链接建立成功后到客户端接收服务端响应结果的时间
幂等性操作:多次访问结果(结果是指最终数据)不变(读操作)
非幂等性操作:多次访问结果变化(写操作)
Feign的负载均衡使用的是Ribbon,Ribbon默认连接超时为1s,超出时间Ribbon默认会重试一次,重试未果则会抛出异常,对于幂等性操作可重试,但非幂等性操作不能重试(在G版本的Spring Cloud中对重试机制进行了调整,重试机制配置只针对非幂等性操作,对于幂等性操作不考虑重试机制配置)
Ribbon是基于REST开发(get-读、post-新增、put-修改、delete-删除),那么get请求必然是幂等性操作,对于其他请求方式必然是非幂等性操作,所以重试机制是针对请求方式生效的(开发时需要注意所有写操作必须约束为非get请求)
建议为每个服务定义超时策略,如果部分服务使用同样的超时策略,可使用全局配置(超时策略的使用优先级:指定服务的超时策略 > 全局配置的超时策略 > 默认的超时策略)
配置全局超时策略
# 配置请求连接的超时时间(单位s,默认1s)
ribbon.ConnectTimeout=1000
# 配置请求处理的超时时间(单位s,默认1s)
ribbon.ReadTimeout=2000
配置指定服务超时策略(eureka-application-service为具体的服务名称)
# 对所有请求都进行重试
erueka-application-service.ribbon.OkToRetryOnAllOperations=true
# 配置重试次数
erueka-application-service.ribbon.MaxAutoRetries=2
# 配置请求连接超时时间
erueka-application-service.ribbon.ConnectTimeout=2000
# 配置请求处理超时时间
eureka-applicaiton-service.ribbon.ReadTimeout=2000
4.Hystrix - Circuit Breaker
hystrix是一个容错管理工具,实现了断路器模式,通过控制服务的节点,从而对延迟和故障提供更强大的容错能力
1.灾难性雪崩效应
灾难性雪崩效应是指服务链中的某一个服务不可用导致一系列的服务不可用,最终造成服务逻辑崩溃
原因分析:
1.服务提供者不可用:硬件故障、程序bug、缓存击穿、并发请求量过大等
2.重试加大流量:用户重试、代码重试逻辑等
3.服务消费方不可用:同步请求阻塞造成的资源耗尽等
常用解决方案:服务降级、请求合并、熔断、隔离、请求缓存…
2.服务降级
在请求超时、资源不足时进行服务降级处理,即不调用真实服务逻辑,而是使用快速失败方式(fallback)直接返回一个托底数据,保证服务链条的完整,避免服务雪崩
简单实现:
1.注册中心
依赖
<dependencyManagement>
<dependencies>
<!--管理spring boot资源-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.1.10.RELEASE</version>
<type>pom</type>
<!--只引入资源-->
<scope>import</scope>
</dependency>
<!--管理spring cloud资源-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Greenwich.SR4</version>
<type>pom</type>
<!--只引入资源-->
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<!--web资源-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.2.1.RELEASE</version>
</dependency>
<!--eureka server资源-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
</dependencies>
配置
# tomcat端口(保持与eureka端口一致)
server.port=8761
# 服务名称
spring.application.name=eureka-server-registry01
启动类
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
@SpringBootApplication
@EnableEurekaServer
public class App {
public static void main(String[] args) {
SpringApplication.run(App.class,args);
}
}
2.远程服务接口
依赖
<dependencyManagement>
<dependencies>
<!--管理spring boot资源-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.1.10.RELEASE</version>
<type>pom</type>
<!--只引入资源-->
<scope>import</scope>
</dependency>
<!--管理spring cloud资源-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Greenwich.SR4</version>
<type>pom</type>
<!--只引入资源-->
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<!--web资源-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--feign资源-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-openfeign-core</artifactId>
</dependency>
</dependencies>
接口
import cn.khue.service.impl.RemoteServiceImpl;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RequestMapping;
@FeignClient(value = "eureka-service-provider01",fallback = RemoteServiceImpl.class)
public interface RemoteService {
@RequestMapping("/test")
String test();
}
降级实现类
import cn.khue.service.RemoteService;
import org.springframework.stereotype.Service;
@Service
public class RemoteServiceImpl implements RemoteService {
@Override
public String test(String name, int id) {
return "test02";
}
}
3.服务提供方
依赖
<dependencyManagement>
<dependencies>
<!--管理spring boot资源-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.1.10.RELEASE</version>
<type>pom</type>
<!--只引入资源-->
<scope>import</scope>
</dependency>
<!--管理spring cloud资源-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Greenwich.SR4</version>
<type>pom</type>
<!--只引入资源-->
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<!--web资源-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--eureka server资源-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!--远程服务接口资源-->
<dependency>
<groupId>cn.khue</groupId>
<artifactId>remote-service</artifactId>
<version>1.0-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
</dependencies>
配置
# tomcat端口
server.port=8081
# 服务名称
spring.application.name=eureka-service-provider01
# 注册中心
eureka.client.service-url.defaultZone=http://localhost:8761/eureka/
控制类
import cn.khue.service.TestService;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class TestController implements RemoteService {
@RequestMapping("/test")
public String test(){
//模拟雪崩
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "test";
}
}
启动类
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
@SpringBootApplication
@EnableEurekaClient
public class App {
public static void main(String[] args) {
SpringApplication.run(App.class,args);
}
}
4.服务消费方
依赖
<dependencyManagement>
<dependencies>
<!--管理spring boot资源-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.1.10.RELEASE</version>
<type>pom</type>
<!--只引入资源-->
<scope>import</scope>
</dependency>
<!--管理spring cloud资源-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Greenwich.SR4</version>
<type>pom</type>
<!--只引入资源-->
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<!--web资源-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--eureka server资源-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!--feign资源-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!--thymeleaf资源-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!--远程服务接口资源-->
<dependency>
<groupId>cn.khue</groupId>
<artifactId>remote-service</artifactId>
<version>1.0-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
</dependencies>
配置
# tomcat端口
server.port=8082
# 服务名称
spring.application.name=eureka-service-consumer01
# 注册中心
eureka.client.service-url.defaultZone=http://localhost:8761/eureka/
# 开启gzip
server.compression.enabled=true
# 开启hystrix
feign.hystrix.enabled=true
控制类
import cn.khue.service.RemoteService;
import org.springframework.stereotype.Controller;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
public class ConsumerController {
@Autowired
@Qualifier("remoteServiceImpl")
private RemoteService remoteService;
@RequestMapping("/test")
public String test(){
return remoteService.test();
}
}
启动类
import org.springframework.boot.SpringApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.cloud.netflix.hystrix.EnableHystrix;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
@EnableEurekaClient
@EnableFeignClients
@EnableHystrix
public class App {
public static void main(String[] args) {
SpringApplication.run(App.class,args);
}
}
3.请求合并
请求合并是指在一定时间内,收集一定量的同类型请求,合并请求后,一次性访问服务提供方,获取批量结果
4.熔断/断路由
熔断是指在一定时间内,异常请求(请求超时、网络故障、服务异常等)比例达到阈值时,启动熔断器,停止调用具体服务逻辑,通过fallback快速返回托底数据,保证服务链的完整
熔断有自动恢复机制,当熔断器启动后,每隔5s尝试将新的请求发送给服务提供方,如果正常响应,则会关闭熔断器恢复服务
5.Dashboard
Hystrix Dashboard是一款针对Hystrix实时监控的工具,通过Hystrix Dashboard可以直观地看到Hystrix Command的请求响应时间、请求成功率等数据
5.Zuul - Router And Filter
zuul是微服务网关,提供动态路由、访问过滤等服务
网关的一般职责:
1.统一入口 - 为全部微服务提供唯一入口,隔离内外,保障后台服务的安全性
2.鉴权校验 - 识别每个请求的权限,拒绝不符合要求的请求
3.动态路由 - 动态将请求路由到不同的后端集群
4.减少耦合 - 减少客户端与服务的耦合,服务可独立发展,通过网关层映射
1.简单实现
1.依赖
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
2.启动类
//开启zuul网关服务
@EnableZuulProxy
3.全局配置
spring.application.name=spring-cloud-zuul
server.port=8888
# 服务注册中心地址
eureka.client.serviceUrl.defaultZone=http://localhost:8761/eureka/
# url路径匹配方式
# 配置路径匹配路由规则(eureka-application-client为要调用的服务名称)
# 可用通配符 ?表示单个字符, *表示任意多个字符,**表示任意多个字符,但包含多级路径
zuul.routes.eureka-application-client.path=/api/**
# 配置符合path的请求路径路由到的服务地址
zuul.routes.erueka-application-client.url=http://127.0.0.1:8082
# 服务名称匹配方式
# 配置路径匹配路由规则
zuul.routes.eureka-application-client.path=/api/**
# 配置符合path的请求路径路由到的服务名称(本配置也可省略,默认服务名称为eureka-application-client)
zuul.routes.eureka-application-client.serviceId=eureak-application-clietn
2.过滤器使用
zuul提供过滤器定义,可用来过滤代理请求,提供额外功能逻辑(如权限验证、日志记录等)
zuul过滤类型:
1.前置过滤:请求进入zuul之后立即执行
2.路由后过滤:zuul实现请求路由后,在远程服务调用之前执行
3.后置过滤:远程服务调用结束后执行
4.异常过滤:任意过滤器发生异常或远程服务调用无果(超时)反馈时执行
简单实现:
@Component
public class MyZuulFilter extends ZuulFilter{
//是否开启filter
@Override
public boolean shouldFilter(){
return true;
}
//具体逻辑
@Override
public Object run() throws ZuulException{
...
}
//过滤器类型:pre-前置过滤,route-路由后过滤,post-后置过滤,error-异常过滤
@Override
public String filterType(){
return "pre";
}
//同类型过滤器的执行顺序,数值越小,优先级越高
@Override
public int filterOrder(){
return 0;
}
}
生命周期:
3.网关容错
zuul启动器中包含了Hystrix的相关依赖,zuul提供FallbackProvider接口用于实现fallback处理,但只针对timeout异常处理,只要服务有返回(包括异常),都不会触发zuul的fallback
全局配置
# 配置http连接超时
zuul.host.connect-timeout-millis=4000
# 配置socket连接超时
zuul.host.socket-timeout-millis=4000
# 配置请求连接超时(默认1000)
ribbon.ConnectTimeout=3000
# 配置请求处理超时
ribbon.ReadTimeout=3000
实现代码
@Component
public class MyZuulFallback implements FallbackProvider{
//fallback需要处理哪个服务
@Override
public String getRoute(){
return "eureka-application-client";
}
//具体逻辑
@Override
public ClientHttpResponse fallbackResponse(String route, Theowable cause){
...
}
}
6.Archaiusuul
Archaius包含一系列配置管理API,提供动态类型化属性、线程安全配置操作、轮询框架、回调机制等功能
2.Spring Cloud Config
spring cloud config 采用集中式管理每个微服务的配置信息,并使用git等版本仓库统一存储配置内容,实现了版本化管理控制,微服务使用rest方式与配置中心交互实现可扩展的配置服务
1.简单实现
1.config server
依赖
<artifactId>Spring-boot-starter-web</artifactId>
<artifactId>Spring-cloud-starter-netflix-eureka-client</artifactId>
<artifactId>Spring-cloud-config-server</artifactId>
全局配置
spring.appliation.name=spring-cloud-config-server
server.port=8090
# 配置git远程仓库地址
spring.cloud.config.server.git.uri=https://gitee.com/khue/test.git
# 配置自由仓库用户名和密码(公共仓库无需配置)
spring.cloud.config.server.git.username=root
spring.cloud.config.server.git.password=root
# 注意:微服务的配置文件命名有严格规定:appliationName-profile.properties或applicationName-profile.yml
# applicationName为被管理配置文件的微服务名称
# profile为被管理配置文件的微服务环境(开发环境的dev、测试环境的test等),未定义则代表默认环境default
启动类
@SpringBootApplication
@EnableConfigServer
@EnableEurekaClient
public class App{
...
}
浏览器测试获取的固定格式:http://config-server-ip:port/applicationName/profile[/label]
label为git中的分支命名,默认为master,如果不是master,则需要提供分支名
2.config client
依赖
<artifactId>spring-cloud-starter-config</artifactId>
全局配置
# 配置配置文件名称
spring.cloud.config.name=eureka-application-client
# 配置config server地址
spring.cloud.config.uri=http://localhost:8090
# 配置配置文件具体环境
spring.cloud.profile=dev
# 配置git分支名称
spring.cloud.config.lable=master
获取配置文件内容:使用@Value注解
@Value("${db.mysql.username}")
private String username;
...
2.热刷新
spring cloud config的热刷新是指应用不重启,通过post请求访问/actuator/refresh实现环境的变化
依赖
<artifactId>spring-boot-starter-actuator</artifactId>
全局配置
management.endpoints.web.exposure.include=/refresh
如果要刷新bean对象,需要在对应的类型上增加@RefreshScope注解
3.安全认证
因为配置中心是完全对外公开的,可以直接通过http://config-server-ip:port/applicationName/profile/lable直接获取配置文件内容,这对配置信息时不安全的,spring boot提供security组件可使用http bastic用户安全认证
依赖
<artifactId>spring-boot-starter-security</artifactId>
spring-cloud-config-server全局配置
spring.security.user.name=root
spring.security.user.password=123
spring-cloud-config-client全局配置
spring.cloud.config.username=root
spring.cloud.config.password=123
3.Spring Cloud Bus
spring cloud bus 集成了RabbitMQ、Kafka等市面上常见的消息代理,通过连接微服务系统中所有拥有bus总线机制的节点,当有数据变更的时候,会通过消息中间件使用消息广播的方式通知所有的微服务节点同步更新数据
1.基于config client实现全局刷新
spring cloud bus 提供一个post请求的/actuator/bus-refresh服务来实现应用的热刷新
1.所有需要刷新的config client添加依赖
<artifactId>spring-cloud-starter.bus-amqp</artifactId>
2.配置config client
# 配置rabbitmq
spring.rabbitmq.host=192.168.54.130
spring.rabbitmq.port=5672
spring.rabbitmq.username=root
spring.rabbitmq.password=123
# 配置热刷新
spring.cloud.bus.enabled=true
spring.cloud.bus.trace.enabled=true
management.endpoints.web.exposure.include=bus-refresh,health,info
4.Spring Cloud Stream
spring cloud stream 是spring数据集成的一个组成部件,为开发人员提供了更见简易的外部系统连接方式,可以让微服务开发进一步解耦,让服务开发人员将注意力集中在业务逻辑的处理上
spring cloud stream 对消息中间件提供了进一步的封装,可以做到代码层面无感知的与中间件交互,甚至可以做到动态切换中间组件
1.简单示例
在默认环境下,使用的rabbitmq中的exchange是topic类型,provider发送的消息会被所有的对应consumer同时处理,且默认创建的队列都是auto-delete
这里的consumer集群每个节点都会监听一个queue,queue的名称是exchange名称.随机后缀名,所以每个consumer都在处理不同的queue,而topic类型的exchange会根据路由键来分发消息,其路由键的匹配规则是exchange名称.*,所以同一个消息会发送到所有匹配规则的queue中
1.依赖
<artifactId>spring-cloud-starter-stream-rabbit</artifactId>
2.配置消息provider和consumer
spring.rabbitmq.host=192.168.54.130
spring.rabbitmq.port=5672
spring.rabbitmq.username=root
spring.rabbitmq.password=123
# 配置连接访问rabbitmq虚拟主机路径
spring.rabbitmq.virtualHost=/
3.消息provider代码
public interface ProviderMessage{
@Output("test-stream")
SubscribableChannel getChannel();
}
@Controller
public class ProviderMessageController{
@Autowired
private ProviderMessage providerMessage;
@RequestMapping(value="/message", produces={"application/json;charset=utf-8"})
@ResponseBody
public String sendMessage(String message){
Message<String> msg=MessageBuilder.withPayload(message).build();
providerMessage.getChannel().send(msg);
return "{'status':'ok'}";
}
}
@SpringBootApplication
@EnableEurekaClient
@EnableBinding({ProviderMessage.class})
public class ProviderApp{
public static void main(String[] args){
SpringApplication.run(ProviderApp.class, args);
}
}
4.消息consumer代码
public interface ConsumerMessage{
@Input("test-stream")
SubscribableChannel recive();
}
@Service
@EnableBinding({ConsumerMessage.class})
public class MessageService{
@StreamListener("test-stream")
public void getMessage(String message){
System.out.println(message);
}
}
@SpringBootApplication
@EnableEurekaClient
@EnableBinding({ConsuemrMessage.class})
public class ConsumerApp{
public static void main(String[] args){
SpringApplication.run(ConsumerApp.class, args);
}
}
2.消息分组
消息分组可以实现消息点对点的传递,且可以将队列变更为持久队列,避免消息丢失
分组使用exchange类型仍旧是topic,只是将consumer集群注册到了同一个queue中
分组后使用的队列是持久化队列,不会因为consumer全部关闭而自动删除,所以可以有效避免消息丢失
1.配置消息provider
# outputName为@Output的value值
# test-exchange为对应的exchange
spring.cloud.stream.bindings.outputName.destination=test-exchange
2.配置消息consumer
# intputName为@Input的value值
# test-exchange为对应的exchange
spring.cloud.stream.bindings.inputName.destination=test-exchange
# 定义分组实质上是指定队列命名,具体队列名称为exchange名称.队列后缀名
spring.cloud.stream.bindings.inputName.group=queue-group
3.消息分区
消息分区可以实现相同的消息一定发送给同一个consumer节点处理,避免队列中有重复消息被不同consumer处理的情况
分区操作是由spring cloud 控制,而非rabbitmq,分区操作使用的exchange类型依然是topic
分区控制是根据路由键routing-key实现的,spring cloud stream 在发送消息之前会判断payload是否曾经发送过,如果发送过则使用之前的routing-key,如果没有发送过,则可以随机生成有效的routing-key(路由键取值:交换器名称-分区序号)
spring cloud stream 中记录payload是否重复的方式是ConcurrentHashMap
如果消息极多且服务运行为7*24,则不推荐分区,因为ConcurrentHashMap会占用内存,服务时间太长,消息太多,会导致内存泄露
1.配置provider
# 配置分组信息
spring.cloud.stream.bindings.outputName.destination=test-exchange
# 配置分区信息
# payload代表根据内容分区
spring.cloud.stream.bindings.outputName.producer.partitionKeyExpression=payload
# 配置分区数量,具体数据根据业务需求而定
spring.cloud.stream.bindings.outputName.producer.partitionCount=2
2.配置consumer
# 配置分组信息
spring.cloud.stream.bindings.inputName.destination=test-exchange
spring.cloud.stream.bindings.inputName.group=queue-group
# 开启分区控制
spring.cloud.stream.bindings.inputName.consumer.partitioned=true
# 配置分区数量
spring.cloud.stream.instanceCount=2
# 配置当前consumer分区编号(从0开始,自然数升序排列)
spring.cloud.stream.instanceIndex=0