赞
踩
微服务(Microservices)是一种软件架构风格,它是以专注于单一责任与功能的小型功能区块(Small Building Blocks) 为基础,利用模块化的方式组合出复杂的大型应用程序,各功能区块使用与语言无关 (Language-Independent/Language agnostic)的API集相互通信。
一个单块应用系统是以一个单个单元的方式来构建的。企业应用系统经常包含三个主要部分:客户端用户界面、数据库和服务端应用系统。这里的服务端应用系统就是一个单体的应用,系统中任意逻辑发生变化都会导致重新构建部署一个新版本的服务端应用系统。针对单体应用,当访问量变大时,通常采用负载均衡,横向扩展的方式将多个单体应用部署到多个服务器上访问。
软件变更受到了很大的限制,应用系统的一个很小的部分的一处变更,也需要将整个单块应用系统进行重新构建和部署。不能根据用户需求部署应用系统中的功能模块,只能扩展部署整个应用系统。
要搭建一个微服务,运维和部署都变得非常复杂,spring提供了一套解决方案:
springBoot:快速构建单个服务;
springcloud:是一系列有序框架的集合,其主要的设施有,服务发现与注册,配置中心,消息总线,负载均衡,断路器,数据监控等,通过Spring Boot的方式,可以实现一键启动,和部署。
Spring cloud data flow: 为基于微服务的分布式流处理和批处理数据通道提供了一系列模型和最佳实践.
Spring-Boot是由Pivotal团队提供的全新框架,其设计目的是用来简化新Spring应用的初始搭建以及开发过程。个人理解来说Spring-Boot其实不是什么新的框架,它默认配置了很多框架的使用方式,就像maven整合了所有的jar包,Spring-Boot整合了其他相关联框架。
1、快速构建项目。
2、对主流开发框架的无配置集成。
3、项目可独立运行,无须外部依赖Servlet容器。
4、提供运行时的应用监控。
5、极大的提高了开发、部署效率。
6、与云计算的天然集成。
1、独立运行Spring项目
Spring Boot 可以以jar包形式独立运行,运行一个Spring Boot项目只需要通过java -jarxx.jar来运行。
2、内嵌servlet容器
Spring Boot可以选择内嵌Tomcat、jetty或者Undertow,这样我们无须以war包形式部署项目。
3、提供starter简化Maven配置
spring提供了一系列的start pom来简化Maven的依赖加载,例如,当你使用了spring-bootstarter-web,会自动加入如图5-1所示的依赖包。
4、自动装配Spring
Spring Boot会根据在类路径中的jar包,类、为jar包里面的类自动配置Bean,这样会极大地减少我们要使用的配置。当然,Spring Boot只考虑大多数的开发场景,并不是所有的场景,若在实际开发中我们需要配置Bean,而Spring Boot灭有提供支持,则可以自定义自动配置。
5、准生产的应用监控
Spring Boot提供基于http ssh telnet对运行时的项目进行监控。
6、无代码生产和xml配置
Spring Boot不是借助与代码生成来实现的,而是通过条件注解来实现的,这是Spring4.x提供的新特性。
使用Spring Initializr的Web页面创建项目
1、打开 https://start.spring.io/
2、填写项目信息
3、点击”Generate Project“按钮生成项目;下载此项目
4、解压项目包,并用IDEA以Maven项目导入,一路下一步即可,直到项目导入完毕。
5、如果是第一次使用,可能速度会比较慢,包比较多、需要耐心等待一切就绪。
使用IDEA直接创建项目
1、创建一个新项目
2、选择spring initalizr , 可以看到默认就是去官网的快速构建工具那里实现
3、填写项目信息
4、选择初始化的组件(初学勾选 Web 即可)
5、填写项目路径
6、等待项目构建成功
File —> New —> New Project —> Spring Initializr
src/main/java路径:主要编写业务程序
src/main/resources路径:存放静态文件和配置文件
src/test/java路径:主要编写测试程序
Springboot01Application: 程序的主入口
application.properties: Spring Boot核心配置文件
Test : 单元测试
<!-- 父依赖 --> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.2.5.RELEASE</version> <relativePath/> </parent> <dependencies> <!-- web场景启动器 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- springboot单元测试 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> <!-- 剔除依赖 --> <exclusions> <exclusion> <groupId>org.junit.vintage</groupId> <artifactId>junit-vintage-engine</artifactId> </exclusion> </exclusions> </dependency> </dependencies> <build> <plugins> <!-- 打包插件 --> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build>
在主程序的同级目录下,创建一个controller包。
在包中新建一个HelloController类
@RestController
public class HelloController {
@RequestMapping("/hello")
public String hello(){
//调用业务,前端接收参数
return "Hello Controller";
}
}
这就完成了web接口的开发。端口号默认为8080,如果想要修改,可以在application.yml文件中使用server.port来指定端口:
server:
port:8081
将项目打包成jar包,点击maven的package
打包成功后会在target目录下生成一个jar包
SpringBoot使用一个全局的配置文件,配置文件名是固定的;默认使用以下两种格式:
• application.properties
语法结构:key=value
• application.yml
语法结构:key:空格 value
配置文件的作用:修改SpringBoot自动配置的默认值;Spring Boot启动时会根据配置文件自动注册相关的应用组件;
YAML:以数据为中心,比json、xml等更适合做配置文件
使用缩进表示层级关系
缩进时不允许使用Tab键,只允许使用空格。
缩进的空格数目不重要,只要相同层级的元素左侧对齐即可 – 大小写敏感
键值对中间必须要有空格k:(空格)v
YAML支持三种数据结构:
字面量:普通的值(数字,字符串,布尔)
server:
port: 8081
**注意:**字符串默认不用加上单引号或者双引号;
双引号:特殊符号表示转义符本身;
name: “zhangsan \n lisi”:输出;zhangsan 换行 lisi
单引号:特殊字符就表示字符本身;
name: ‘zhangsan \n lisi’:输出;zhangsan \n lisi
对象、Map(属性和值)(键值对)
person:
name: zhangsan
age: 12
另一种行内写法:
person: {name: zhangsan,age: 12}
数组(List,Set)
hobbies:
- singing
- dancing
- runing
另一种写法:
hobbies: [singing,dancing,running]
//只有spring容器中的对象才能自动进行数据绑定 @Component //将本类中的所有属性和配置文件中相关的配置进行绑定; // prefix指定要绑定的配置文件中的属性前缀; @ConfigurationProperties(prefix = "person") public class Person { private String pname; private int age; private boolean success; private Date birth; private Car car; private Map<String,Object> maps; private List<Object> lists; //有参无参构造、get、set方法、toString()方法 }
在SpringBoot项目中的resources目录下新建一个文件application.yml
person:
pname: zhangsan
age: 12
birth: 2020/12/12
success: true
car:
cname: 奔驰
cprice: 200.0
lists: [唱歌,跳舞]
maps: {key1: v1,key2: v2
@SpringBootTest
class SpringbootApplicationTests {
@Autowired
private Person person;
@Test
void contextLoads() {
System.out.println(person);
}
}
以KEY=VALue键值对的方式设置值
server.port=8081
person.name=张三
person.age=12
maps.key1=value1
maps.key2=value2
hobbies=singing,dancing,running
//只有spring容器中的对象才能自动进行数据绑定 @Component //将本类中的所有属性和配置文件中相关的配置进行绑定; // prefix指定要绑定的配置文件中的属性前缀; @ConfigurationProperties(prefix = "person") public class Person { private String pname; private int age; private boolean success; private Date birth; private Car car; private Map<String,Object> maps; private List<Object> lists; //有参无参构造、get、set方法、toString()方法 }
person.pname=张三
person.age=12
person.birth=2019/12/12
person.success=true
person.car.cname=宝马
person.car.cprice=100.0
person.lists=唱歌,跳舞,跑步
person.maps.k1=value1
person.maps.k2=value2
@SpringBootTest
class SpringbootApplicationTests {
@Autowired
private Person person;
@Test
void contextLoads() {
System.out.println(person);
}
}
1、springboot全局配置文件中可以使用随机数
r a n d o m . v a l u e 、 {random.value}、 random.value、{random.int}、 r a n d o m . l o n g 、 {random.long}、 random.long、{random.int(10)}、${random.int[1024,65536]}
2、springboot全局配置文件中可以使用占位符
通过${属性名}获取已经在配置文件中加载过的属性值;
通过${属性名:默认值}来指定找不到属性时的默认值;
person.pname=张三
person.age=${random.int}
person.success=true
person.birth=2012/12/12
person.car.cname=${car.cname:奔驰}
person.car.cprice=200.0
person.map.key1=value1
person.map.key2=value2
person.hobbies=唱歌,跳舞,跑步
1、@ConfigurationProperties注解用于根据配置文件批量注入值,springboot默认配置文件application.yml/application.properties;
2、@Value注解用于根据配置文件单个注入值;
@ConfigurationProperties | @value | |
---|---|---|
功能 | 根据配置文件批量注入值 | 单个注入值 |
SpEL | 不支持 | 支持 |
复杂类型封装 | 支持 | 不支持 |
松散绑定 | 支持 | 不支持 |
JSR303数据校验 | 支持 | 不支持 |
Springboot中可以用@validated来校验数据,如果数据异常则会统一抛出异常,方便异常中心统一处理。
添加依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
Person实体类
@Component //注册bean
@ConfigurationProperties(prefix = "person")
@Validated //数据校验
public class Person {
private String name;
private int age;
private Boolean happy;
private Map<String,String> address;
private List<String> list;
@Email(message = "邮箱格式不正确")//必须是邮箱格式
private String email;
//有参无参构造、set、get、toStrin
yaml
person:
name: 张三
age: 12
happy: true
address: {home1: 001,home2: 002}
list:
- mom
- dad
- son
email: why@163.com
Profile是Spring对不同环境提供不同配置功能的支持,可以通过激活、指定参数等方式快速切换环境;
格式:application-{profile}.properties/yml
可以在项目中创建如下主配置文件:application-dev.properties、application-test.properties、application-prod.properties、application.properties,默认使用application.properties。
可以通过配置spring.profiles.active=profile指定使用某个环境的配置文件。
#通过---来区分不同的profile环境 spring: profiles: active: test #选择要激活哪个环境块 --- spring: config: activate: on-profile: dev #配置环境的名称 server: port: 8888 --- spring: config: activate: on-profile: test server: port: 9999
注意:如果yml和properties同时都配置了端口,并且没有激活其他环境 , 默认会使用properties配置文件的!
在配置文件中指定 spring.profiles.active=dev
命令行 java -jar springboot-0.0.1-SNAPSHOT.jar --spring.profiles.active=dev
jvm参数 -Dspring.profiles.active=dev
springboot默认会从以下位置加载配置文件:application.properties/application.yml
项目所在磁盘路径file:./config/
项目所在磁盘路径file:./
项目类路径classpath:/config/
项目类路径classpath:/
优先级由高到底,高优先级的配置会覆盖低优先级的配置;SpringBoot会从这四个位置全部加载主配置文件,互补配置;
如果要加载其他位置的配置文件,可以通过–spring.config.location(只能加到命令行)来指定要加载的配置文件的位置。
点击跳转:https://blog.csdn.net/m0_67296957/article/details/130480210
SpringBoot启动时会根据配置文件自动配置相应场景的组件xxxAutoConfiguration,web项目启动时会初始化WebMvcAutoConguration组件处理请求相关操作。
其中有默认处理静态资源的方法:addRedourceHandlers 添加资源处理
@Override public void addResourceHandlers(ResourceHandlerRegistry registry) { if (!this.resourceProperties.isAddMappings()) { // 已禁用默认资源处理 logger.debug("Default resource handling disabled"); return; } // 缓存控制 Duration cachePeriod = this.resourceProperties.getCache().getPeriod(); CacheControl cacheControl = this.resourceProperties.getCache().getCachecontrol().toHttpCacheControl(); // webjars 配置 if (!registry.hasMappingForPattern("/webjars/**")) { customizeResourceHandlerRegistration(registry.addResourceHandler("/webjars/**") .addResourceLocations("classpath:/META-INF/resources/webjars/") .setCachePeriod(getSeconds(cachePeriod)).setCacheControl(cacheControl)); } // 静态资源配置 String staticPathPattern = this.mvcProperties.getStaticPathPattern(); if (!registry.hasMappingForPattern(staticPathPattern)) { customizeResourceHandlerRegistration(registry.addResourceHandler(staticPathPattern) .addResourceLocations(getResourceLocations(this.resourceProperties.getStaticLocations())) .setCachePeriod(getSeconds(cachePeriod)).setCacheControl(cacheControl)); } }
从该方法中可以发现:
1、匹配/wenjars/**的请求,都去classpath:/META-INF/resources/webjars/找资源。
Webjars本质就是以jar包的方式引入我们的静态资源,我们以前要导入一个静态文件可以直接导入。
<!--jquery的jar包引入-->
<dependency>
<groupId>org.webjars</groupId>
<artifactId>jquery</artifactId>
<version>3.6.0</version>
</dependency>
导入完毕,查看webjars目录结构,并访问Jquery.js文件。
浏览器访问路径:http://localhost:8080/webjars/jquer/3.6.0/jquery.min.js获取相应的静态资源。
默认静态资源路径:
“classpath:/META-INF/resources/”
“classpath:/resources/”
“classpath:/static/”
“classpath:/public/”
“/”:当前项目的根路径
优先级:resources > static > public
在配置文件中通过属性spring.web.resources.static-locations=自定义路径,设置自定义静态资源存放路径。
案例:
a、配置文件中设置:spring.web.resources.static-locations=classpath:/hello/
b、在自定义路径下添加静态资源hello.html,浏览器访问路径:http://localhost:8080/hello.html获取相应的静态资源。
**注意:**自定义静态资源路径后,默认静态资源路径失效!
点击跳转:https://blog.csdn.net/m0_67296957/article/details/130473375
Thymeleaf 官网:https://www.thymeleaf.org/
Thymeleaf是一款用于渲染XML/XHTML/HTML5内容的模板引擎。类似JSP,Velocity,FreeMaker等。它也可以轻易的与Spring MVC等Web框架进行集成作为Web应用的模板引擎。Thymeleaf最大的特点是能够正确显示模板页面,而不需要启动整个Web应用。
Thymeleaf 模板引擎的特点:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
<version>2.4.3</version>
</dependency>
spring:
#开始thymeleaf设置
thymeleaf:
#禁用模板缓存
cache: false
package com.offcn.demo.controller; @Controller public class FirstThymeleafController { /** * 访问http://localhost:8080/first * 将数据message填充到templates/index.html * @param model * @return */ @GetMapping("/first") public String indexPage(Model model) { String message = "Hello, Thymeleaf!"; model.addAttribute("message", message); return "index"; } }
在resources/templates下新建index.html
<!doctype html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>首页</title>
</head>
<body>
<h1 th:text="${message}"></h1>
</body>
</html>
http://localhost:8080/first
Thymeleaf 作为一种模板引擎,它拥有自己的语法规则。Thymeleaf 语法分为以下 2 类:
标准表达式语法
th 属性
Thymeleaf 模板引擎支持多种表达式:
使用变量表达式可以获取对象的属性和方法。例如,获取 person 对象的 lastName 属性,表达式形式如下:
${person.lastName}
使用变量表达式还可以使用内置基本对象,获取内置对象的属性,调用内置对象的方法。Thymeleaf中常用的内置基本对象如下:
我们通过以下 2 种形式,都可以获取到 session 对象中的 map 属性:
${#session.getAttribute('map')}
${session.map}
除了能使用内置的基本对象外,变量表达式还可以使用一些内置的工具对象。
我们可以使用内置工具对象 strings 的 equals 方法,来判断字符串与对象的某个属性是否相等,代码如下。
${#strings.equals('why',name)}
选择变量表达式与变量表达式功能基本一致,只是在变量表达式的基础上增加了与 th:object 的配合使用。
当使用 th:object 存储一个对象后,我们可以在其后代中使用选择变量表达式(*{…})获取该对象中的属性,其中,“**”即代表该对象。
<div th:object="${session.user}" >
<p th:text="*{fisrtName}">firstname</p>
</div>
th:object 用于存储一个临时变量,该变量只在该标签及其后代中有效。
无论是静态资源的引用,还是 form 表单的请求,凡是链接都可以用链接表达式 (@{…})。
无参请求:
<a href="http://www.baidu.com">传统写法:跳转至百度</a><br/>
<a th:href="@{http://www.baidu.com}">路径表达式:跳转至百度</a><br/>//绝对路径
<a th:href="@{/user/detail1}">跳转至:/user/detail1</a><br/>//相对路径
有参请求:
<a href="http://localhost:8080/test?username='zhangsan'">传统写法,带参数:/test,并带参数username</a><br/>
<a th:href="@{http://localhost:8080/test?username=zhangsan}">路径表达式写法,带参数:/test,并带参数username</a><br/>//绝对路径
<a th:href="@{/test?username=lisi}">相对路径,带参数</a>//相对路径
该表达式称之为消息表达式,消息表达式主要用于从消息源中提取消息内容实现国际化。
<p th:utext="#{home.welcome}">Hello,World!</p>
片段引用表达式用于在模板页面中引用其他的模板片段。
以上语法结构说明如下:
属性 | 描述 |
---|---|
th:action | 定义后台控制器的路径,类似标签的 action 属性,主要结合 URL 表达式,获取动态变量 |
th:id | 类似 html 标签中的 id 属性 |
th:text | 文本替换,转义特殊字符 |
th:utext | 文本替换,不转义特殊字符 |
th:object | 在父标签选择对象,子标签使用 *{…} 选择表达式选取值。没有选择对象,那子标签使用选择表达式和 ${…} 变量表达式是一样的效果。 |
th:value | 替换 value 属性 |
th:name | 设置名称 |
th:src | 替换 HTML 中的 src 属性 |
th:each | 遍历,支持 Iterable、Map、数组等。 |
th:style | 设置样式 |
th:onclick | 点击事件 |
th:with | 局部变量赋值运算 |
th:if | 根据条件判断是否需要展示此标签 |
th:insert | 布局标签;将使用 th:fragment 属性指定的模板片段(包含标签)插入到当前标签中 |
th:inline | 内联属性; 该属性有 text,none,javascript 三种取值, 在 |
package com.offcn.demo.bean; public class User { private Integer id; private String name; private int age; public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } }
package com.offcn.demo.controller; import java.util.HashMap; import java.util.Map; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.GetMapping; import com.offcn.demo.bean.User; @Controller public class SecondThymeleafController { @GetMapping("/second") public String indexPage(Model model) { String message = "Hello, Thymeleaf!"; User u = new User(); u.setId(1); u.setName("why"); u.setAge(18); Map<String,Object> map=new HashMap<>(); map.put("src1","1.jpg"); map.put("src2","2.jpg"); model.addAttribute("message", message); model.addAttribute("user", u); model.addAttribute("src", map); return "index2"; } }
<!doctype html> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>首页</title> </head> <body> <h1 th:text="${message}"></h1> <img th:src="${src.src1}"/> </br> <img th:src="${src.src2}"/> </br> <span th:text="${user.id}"></span> <span th:text="${user.name}"></span> <span th:text="${user.age}"></span> </body> </html>
package com.offcn.demo.controller; @Controller public class ThreeThymeleafController { @GetMapping("/three") public String indexPage(Model model) { List<User> list=new ArrayList<User>(); User u1 = new User(); u1.setId(1); u1.setName("张三"); u1.setAge(18); list.add(u1); User u2 = new User(); u2.setId(2); u2.setName("李四"); u2.setAge(28); list.add(u2); User u3 = new User(); u3.setId(3); u3.setName("王五"); u3.setAge(25); list.add(u3); User u4 = new User(); u4.setId(4); u4.setName("麻子"); u4.setAge(888); list.add(u4); model.addAttribute("userList", list); return "index3"; } }
<!doctype html> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>首页</title> </head> <body> <table width="200" style="text-align: center;"> <tr> <th>编号</th> <th>姓名</th> <th>年龄</th> <th>index</th> </tr> <tr th:each="user,iterStat : ${userList}"> <td th:text="${user.id}"></td> <td th:text="${user.name}"></td> <td th:text="${user.age}"></td> <td th:text="${iterStat.index}">index</td> </tr> </table> </body> </html>
package com.offcn.demo.controller;
@Controller
public class FourThymeleafController {
@GetMapping("/four")
public String indexPage(Model model) {
model.addAttribute("userName", 互联网底层民工");
model.addAttribute("href", "https://blog.csdn.net/m0_67296957");
return "index4";
}
}
<!doctype html> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>首页</title> </head> <body> <!-- 给标签赋值 th:text --> <h1 th:text="${userName}"></h1> <!-- 给属性赋值 th:value、th:属性名称 --> <input type="text" name="names" th:value="${userName}"/> </br> <em th:size="${userName}"></em> <!-- 字符串拼接 --> <span th:text="'欢迎来:'+${userName}+'学习!'"></span> </br> <!-- 字符串拼接,方式2 --> <span th:text="|欢迎来:${userName}学习!|"></span> </body> </html>
package com.offcn.demo.controller;
@Controller
public class FiveThymeleafController {
@GetMapping("/five")
public String indexPage(Model model) {
model.addAttribute("flag", "yes");
model.addAttribute("menu", "admin");
model.addAttribute("manager", "manager");
return "index5";
}
}
<!doctype html> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>首页</title> </head> <body> <!-- th:if 条件成立就显示 --> <h1 th:if="${flag=='yes'}" >互联网底层民工</h1> <!-- th:unless 条件不成立就显示 --> <h1 th:unless="${flag=='no'}" >why</h1> <!-- switch选择语句 --> <div th:switch="${menu}"> <p th:case="'admin'">User is an administrator</p> <p th:case="${manager}">User is a manager</p> </div> </body> </html>
如果我们让IDE识别 .css文件,那么我们要用相对路径来引入这个文件。如果我们想要在发布后服务器能够加载这个文件,我们就必须用相对于resources或static的位置来引入静态文件。
显然,一般情况下我们不能兼顾这两个问题,只能要么在编写的时候用相对自己的路径,然后在发布的时候用相对于项目资源文件夹的路径,要么就只能放弃IDE的提示,非常尴尬。
而在Thymeleaf中,我们可很好的处理这一点。在引入资源的时候,我们可以写类似下面的代码:
<link rel="stylesheet" type="text/css" media="all" href="../../css/gtvg.css" th:href="@{/css/gtvg.css}" />
当我们在没有后台渲染的情况下,浏览器会认得href,但是不认得th:href,这样它就会选择以相对与本文件的相对路径去加载静态文件。而且我们的IDE也能识别这样的加载方式,从而给我们提示。
当我们在有后台渲染的情况下,后台会把这个标签渲染为这样:
<link rel="stylesheet" type="text/css" media="all" href="/css/gtvg.css" />
原来的href标签会被替换成相对于项目的路径,因此服务器就能找到正确的资源,从而正确渲染。
fragment类似于JSP的tag,在html中文件中,可以将多个地方出现的元素块用fragment包起来使用。
**定义fragment,**所有的fragment可以写在一个文件里面,也可以单独存在,例如:
<body>
<h1 th:fragment="copy">
© 1999-2022 Offcn.All Rights Reserved
</h1>
</body>
注意: 在Springboot中,默认读取thymeleaf文件的路径是:src/main/resource/templates
package com.offcn.demo.controller;
@Controller
public class SixThymeleafController {
@GetMapping("/six")
public String indexPage(Model model) {
return "index6";
}
}
<!doctype html> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>首页</title> </head> <body> <!-- 把片段的内容插入到当前位置 --> <div style="color: #82ff6c" th:insert="~{footer :: copy}"></div> </br> <!-- 使用片段的内容替换当前标签 --> <div style="color: #82ff6c" th:replace="~{footer :: copy}"></div> </br> <!-- 保留自己的主标签,不要片段的主标签 --> <div style="color: #82ff6c" th:include="~{footer :: copy}"></div> </body> </html>
th:insert:保留自己的主标签,保留th:fragment的主标签。
th:replace:不要自己的主标签,保留th:fragment的主标签。
th:include:保留自己的主标签,不要th:fragment的主标签。
#dates:与java.util.Data对象的方法对应,格式化、日期组件抽取等等
#numbers:格式化数字对象的工具方法
#strings:与java.lang.String对应的工具方法
package com.offcn.demo.controller; @Controller public class SevenThymeleafController { @GetMapping("/seven") public String indexPage(Model model) { //日期时间 Date date = new Date(); model.addAttribute("date", date); //小数的金额 double price=128.5678D; model.addAttribute("price", price); //定义大文本数据 String str="Thymeleaf是Web和独立环境的现代服务器端Java模板引擎,能够处理HTML,XML,JavaScript,CSS甚至纯文本。\r\n" + "Thymeleaf的主要目标是提供一种优雅和高度可维护的创建模板的方式。为了实现这一点,它建立在自然模板的概念上,将其逻辑注入到模板文件中,不会影响模板被用作设计原型。这改善了设计的沟通,弥补了设计和开发团队之间的差距。\r\n" + "Thymeleaf也从一开始就设计了Web标准 - 特别是HTML5 - 允许您创建完全验证的模板,如果这是您需要的\r\n" ; model.addAttribute("strText", str); //定义字符串 String str2="JAVA-why"; model.addAttribute("str2", str2); return "index7"; } }
<!doctype html> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>首页</title> </head> <body> 时间:<span th:text="${#dates.format(date,'yyyy-MM-dd HH:mm:ss')}">4564546</span> </br> 金额:<span th:text="'¥'+${#numbers.formatDecimal(price, 1, 2)}">180</span> </br> <!-- # 这里的含义是 如果 atc.text 这个变量多余60个字符,后面显示... --> <p th:text="${#strings.abbreviate(strText,60)}">内容内容内容</p> <!-- 判断字符串是否为空 --> <span th:if="${!#strings.isEmpty(str2)}">字符串str2不为空</span></br> <!-- 截取字符串,指定长度 --> <span th:text="${#strings.substring(str2,0,4)}">字符串str2的值</span> </body> </html>
点击跳转:https://blog.csdn.net/m0_67296957/article/details/130582523
随着代码的不断更新,开发人员在开发新的接口或者更新旧的接口后,由于开发任务的繁重,往往文档很难持续跟着更新,Swagger 就是用来解决该问题的一款重要的工具,对使用接口的人来说,开发人员不需要给他们提供文档,只要告诉他们一个 Swagger 地址,即可展示在线的 API 接口文档。除此之外,调用接口的人员还可以在线测试接口数据,同样得的,开发人员在开发接口时,同样也可以利用 Swagger 在线接口文档测试接口数据,这给开发人员提供了便利。
优点:
号称世界上最流行的API框架
Restful Api 文档在线自动生成器 => API 文档 与API 定义同步更新
直接运行,在线测试API
支持多种语言 (如:Java,PHP等)
<!-- https://mvnrepository.com/artifact/io.springfox/springfox-swagger2 -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.9.2</version>
</dependency>
<!-- https://mvnrepository.com/artifact/io.springfox/springfox-swagger-ui -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.9.2</version>
</dependency>
@RestController
public class HelloWord {
@RequestMapping("/hello")
public String hello() {
return "hello word";
}
}
@Configuration //配置类
@EnableSwagger2// 开启Swagger2的自动配置
public class SwaggerConfig {
}
Swagger实例Bean是Docket,所以通过配置Docket实例来配置Swagger
@Bean
public Docket docket(){
return new Docket(DocumentationType.SWAGGER_2));
}
查看Docket源文件–》ApiINfo源文件
//配置文档信息
private ApiInfo apiInfo() {
Contact contact = new Contact("why","http://xxx.xxx.com/联系人访问链接", "联系人邮箱");
return new ApiInfo(
"学习Swagger", //标题
"配置Swagger", //描述
"1.0", //版本
"urn:tos",//组织连接
contact, //联系人信息
"Apache 2.0", //许可
"http://www.apache.org/licenses/LICENSE-2.0",//许可链接
new ArrayList<>()//扩展
);
@Bean
public Docket docket(){
return new Docket(DocumentationType.SWAGGER_2).apiInfo(apiInfo());
}
4、启动项目,查看效果
//配置Docket,以配置swagger具体参数
@Bean
public Docket docket(){
return new Docket(DocumentationType.SWAGGER_2).apiInfo(apiInfo())
.select()//通过select()方法配置扫描接口,RequestHandlerSelectors配置如何扫描接口
.apis(RequestHandlerSelectors.basePackage("com.offcn.demo.controller"))//只扫描controller包下面的类
.build();
}
由该图像可以看见:basic-error-controller已经不见了,只显示controller包下面的hello和user。
方法 | 说明 |
---|---|
RequestHandlerSelectors | 配置指定要扫描接口的方式 |
basePackage | 指定要扫描的包 |
any | 扫描全部 |
none() | 不扫描 |
withClassAnnotation | 扫描类上的注解,多数是一个注解的反射对象 |
withMethodAnnotation | 扫描方法上的注解 |
paths | 过滤,只看到过滤的接口 |
enable | 是否开启swagger,默认为true |
regex | 正则表达式 |
ant | 通过ant()控制 |
可以看见,在Docket.class中 enabled=ture,所以默认情况下Swagger为开启状态。
//配置Docket,以配置swagger具体参数
@Bean
public Docket docket(){
return new Docket(DocumentationType.SWAGGER_2).apiInfo(apiInfo())
.enable(false)//将Swagger关闭
.select()
.apis(RequestHandlerSelectors.basePackage("com.offcn.demo.controller"))
.build();
}
application.properties:
spring.profiles.active=dev
application-dev.properties:
server.port=8081
application-pro.properties:
server.port=8082
@Bean
public Docket docket(Environment environment){
//设置要显示的swagger的环境
Profiles profiles = Profiles.of("dev", "test");
//通过acceptsProfiles()方法判断是否处于设定的环境中
boolean b = environment.acceptsProfiles(profiles);
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
.enable(b)
.select()
.apis(RequestHandlerSelectors.basePackage("com.offcn.demo.controller"))
.build();
}
//配置Docket,以配置swagger具体参数 @Bean public Docket docket(Environment environment){ //设置要显示的swagger的环境 Profiles profiles = Profiles.of("dev", "test"); //通过acceptsProfiles()方法判断是否处于设定的环境中 boolean b = environment.acceptsProfiles(profiles); return new Docket(DocumentationType.SWAGGER_2) .apiInfo(apiInfo()) .groupName("why")//配置分组 .enable(b) .select() .apis(RequestHandlerSelectors.basePackage("com.offcn.demo.controller")) .build(); }
运行程序:
@Bean
public Docket docket1(){
return new Docket(DocumentationType.SWAGGER_2).groupName("A");
}
@Bean
public Docket docket2(){
return new Docket(DocumentationType.SWAGGER_2).groupName("B");
}
运行程序:
@ApiModel("用户实体类")
public class User {
@ApiModelProperty("号码")
public Long id;
@ApiModelProperty("用户名")
public String username;
@ApiModelProperty("密码")
public String password;
}
@RestController
public class HelloController {
@GetMapping(value = "/hello")
public String hello(){
//调用业务,前端接收参数
return "Hello Controller";
}
@PostMapping(value = "/user")
public User user(){
return new User();
}
}
Swagger注解 | 说明 |
---|---|
@Api | 作用在模块类上 |
@ApiOperation | 作用在接口方法上 |
@ApiModel | 作用在模型类上 |
@ApiModelProperty | 作用在类方法和属性上 |
@ApiParam | 作用在参数、方法和字段上 |
@RestController public class HelloController { @GetMapping(value = "/hello") public String hello(){ //调用业务,前端接收参数 return "Hello Controller"; } @PostMapping(value = "/user") public User user(){ return new User(); } @ApiOperation("Hello控制类") @GetMapping(value = "hello2") public String hello2(@ApiParam("用户名") String username){ return "Hello"+username; } }
运行程序:
点击跳转:https://blog.csdn.net/qq_22075913/article/details/125148535
点击跳转:https://blog.csdn.net/m0_67296957/article/details/130884073
@Service
public class AsyncService {
public void hello(){
try {
Thread.sleep(3000);//睡眠三秒
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("业务进行中....");
}
}
@RestController
public class AsyncController {
@Autowired
AsyncService asyncService;
@GetMapping("/hello")
public String hello(){
asyncService.hello();
return "success";
}
}
访问http://localhost:8080/hello进行测试,三秒后出现success。这是同步等待。
如果我们想要用户直接得到消息,不需要等待。就需要在后台使用多线程的方式进行处理。
添加该注解后,Springboot会开一个线程池,进行调用。
//告诉Spring这是一个异步方法
@Async
public void hello(){
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("业务进行中....");
}
@EnableAsync //开启异步注解功能
@SpringBootApplication
public class SpringbootTaskApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootTaskApplication.class, args);
}
}
重新启动,访问http://localhost:8080/hello。网页瞬间响应,后台代码依旧执行。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>
查看它引入的依赖,可以看见jakarta.mail
<dependency>
<groupId>com.sun.mail</groupId>
<artifactId>jakarta.mail</artifactId>
<version>1.6.4</version>
<scope>compile</scope>
</dependency>
在设置->账号中启动POP3/SMTP服务和IMAP/SMTP服务
获取授权码,用于在第三方客户端登录邮箱
spring.mail.username=2592814145@qq.com
spring.mail.password=你的qq授权码
#邮件发送器 QQ的是smtp.qq.com 网易邮箱为smtp.163.com
spring.mail.host=smtp.qq.com
# qq需要配置ssl
spring.mail.properties.mail.smtp.ssl.enable=true
获取授权码:在QQ邮箱中的设置->账户->开启pop3和smtp服务
@Autowired JavaMailSenderImpl mailSender; @Test public void contextLoads() { //邮件设置1:一个简单的邮件 SimpleMailMessage message = new SimpleMailMessage(); message.setSubject("通知-明天来狂神这听课"); message.setText("今晚7:30开会"); message.setTo("24736743@qq.com"); message.setFrom("24736743@qq.com"); mailSender.send(message); } @Test public void contextLoads2() throws MessagingException { //邮件设置2:一个复杂的邮件 MimeMessage mimeMessage = mailSender.createMimeMessage(); MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, true); helper.setSubject("通知-明天来狂神这听课"); helper.setText("<b style='color:red'>今天 7:30来开会</b>",true); //发送附件 helper.addAttachment("1.jpg",new File("")); helper.addAttachment("2.jpg",new File("")); helper.setTo("24736743@qq.com"); helper.setFrom("24736743@qq.com"); mailSender.send(mimeMessage); }
基于注解@Scheduled默认为单线程,开启多给任务时,任务的执行时机会受上一个任务执行时间的影响;
@Service
public class ScheduledService {
//秒 分 时 日 月 周几
//注意cron表达式的用法;
@Scheduled(cron = "0/2 * * * * ?)//每两秒打印一次hello
public void hello(){
System.out.println("hello.....")/;
}
}
@EnableAsync //开启异步注解功能 @EnableScheduling //开启基于注解的定时任务 @SpringBootApplication public class SpringbootTaskApplication { public static void main(String[] args) { SpringApplication.run(SpringbootTaskApplication.class, args); } //定时任务也可以写在主程序中 /* @Scheduled(cron = "0/2 * * * * ?)//每两秒打印一次hello public void hello(){ System.out.println("hello.....")/; }*/ }
结构:
cron表达式是一个字符串,分为6或7个域,每两个域之间用空格分隔。
取值范围:
域名 | 可取值 | 可取符号 |
---|---|---|
秒域 | 0~59的整数 | * - , / |
分域 | 0~59的整数 | * - , / |
时雨 | 0~23的整数 | * - , / |
日域 | 1~31的整数 | * - , / ?L |
周域 | 112的整数或JANDEC | * - , / |
月域 | 17的整数或SUNSAT | * - , / ? L # |
年域 | 1970~2099的整数 | * - , / |
常用表达式:
表达式 | 意义 |
---|---|
0/5 * * * * ? | 表示每两秒执行一次任务 |
0 0/2 * * * ? | 表示每两分钟执行任务 |
0 0 2 1 * ? | 表示在每月的一号的凌晨2点调整任务 |
0 15 10 ?* MON-FRI | 表示周一到周五每天上午10:15执行作业 |
0 15 10 ?6L 2002-2006 | 表示2002-2006年的每个月的最后一个星期五上午10:15执行作 |
0 0/5 14 * * ? | 在每天下午2点到下午2:55期间的每5分钟触发 |
0 10,44 14 ? 3 WED | 每年三月的星期三的下午2:10和2:44触发 |
用于实现从数据库获取指定时间来动态执行定时任务;
使用@Scheduled 注解很方便,但缺点是当我们调整了执行周期的时候,需要重启应用才能生效。为了达到实时生效的效果,可以使用接口来完成定时任务,统一将定时器信息存放在数据库中。
DROP DATABASE IF EXISTS `task`;
CREATE DATABASE `task`;
USE `TASK`;
DROP TABLE IF EXISTS `cron`;
CREATE TABLE `cron` (
`cron_id` varchar(30) NOT NULL PRIMARY KEY,
`cron_name` varchar(30) NULL,
`cron` varchar(30) NOT NULL
);
INSERT INTO `cron` VALUES ('1', '0/5 * * * * ?');
spring:
datasource:
url: jdbc:mysql://localhost:3306/task
username: root
password: root
@Configuration //1.主要用于标记配置类,兼备Component的效果。 @EnableScheduling // 2.开启定时任务 public class DynamicScheduleTask implements SchedulingConfigurer { @Mapper public interface CronMapper { @Select("select cron from cron limit 1") public String getCron(); } @Autowired //注入mapper @SuppressWarnings("all") CronMapper cronMapper; /** * 执行定时任务. */ @Override public void configureTasks(ScheduledTaskRegistrar taskRegistrar) { taskRegistrar.addTriggerTask( //1.添加任务内容(Runnable) () -> System.out.println("执行动态定时任务: " + LocalDateTime.now().toLocalTime()), //2.设置执行周期(Trigger) triggerContext -> { //2.1 从数据库获取执行周期 String cron = cronMapper.getCron(); //2.2 合法性校验. if (StringUtils.isEmpty(cron)) { // Omitted Code .. } //2.3 返回执行周期(Date) return new CronTrigger(cron).nextExecutionTime(triggerContext); } ); } }
基于注解设定多线程定时任务;
@Scheduled执行周期任务会受到上次一个任务的执行时间影响。那么可以开启多线程执行周期任务
//@Component注解用于对那些比较中立的类进行注释; //相对与在持久层、业务层和控制层分别采用 @Repository、@Service 和 @Controller 对分层中的类进行注释 @Component @EnableScheduling // 1.开启定时任务 @EnableAsync // 2.开启多线程 public class MultithreadScheduleTask { @Async @Scheduled(fixedDelay = 1000) //间隔1秒 public void first() throws InterruptedException { System.out.println("第一个定时任务开始 : " + LocalDateTime.now().toLocalTime() + "\r\n线程 : " + Thread.currentThread().getName()); System.out.println(); Thread.sleep(1000 * 10); } @Async @Scheduled(fixedDelay = 2000) public void second() { System.out.println("第二个定时任务开始 : " + LocalDateTime.now().toLocalTime() + "\r\n线程 : " + Thread.currentThread().getName()); System.out.println(); } }
运行:
点击跳转:https://blog.csdn.net/m0_67296957/article/details/130901925
Redis基础:https://blog.csdn.net/m0_67296957/article/details/128481080
说明:在SpringBoot2.x之后,原来使用的jedis被替换为了lettuce
jedis:采用的直连,多个线程操作的话是不安全的,如果想要避免不安全的情况,使用jedis pool连接池。更像BIO模式。
lettuce:采用netty,实例可以再多个线程中进行共享,不存在线程不安全的情况,可以减少线程数据。更像NIO模式。
或者是pom.xml文件当中引入启动器依赖
<!--整合redis的启动器-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
server: port: 8080 spring: #redis相关配置 redis: database: 5 # 配置redis的主机地址,需要修改成自己的 host: 192.168.48.190 port: 6379 password: 123456 timeout: 5000 jedis: pool: # 连接池中的最大空闲连接,默认值也是8。 max-idle: 500 # 连接池中的最小空闲连接,默认值也是0。 min-idle: 50 # 如果赋值为-1,则表示不限制;如果pool已经分配了maxActive个jedis实例,则此时pool的状态为exhausted(耗尽) max-active: 1000 # 等待可用连接的最大时间,单位毫秒,默认值为-1,表示永不超时。如果超过等待时间,则直接抛出JedisConnectionException max-wait: 2000
针对jedis客户端中大量api进行了归类封装,将同一类型操作封装为operation接口。
ValueOperations:简单K-V操作
SetOperations:set类型数据操作
ZSetOperations:zset类型数据操作
HashOperations:针对map类型的数据操作
ListOperations:针对list类型的数据操作
@SpringBootTest public class BootRedisDemoApplicationTests { @Autowired private RedisTemplate<String,String> redisTemplate; @Test void test1() { ValueOperations<String, String> ops = redisTemplate.opsForValue(); //ops 存 ops.set("username","jack"); // ops取 String username = ops.get("username"); System.out.println(username); //移除 redisTemplate.delete("username"); } }
@SpringBootTest public class BootRedisDemoApplicationTests { @Autowired private RedisTemplate<String,String> redisTemplate; // set集合类型 @Test void test2() { SetOperations<String, String> ops = redisTemplate.opsForSet(); //存: ops.add("set","jack","marray","george"); //取: Set<String> set = ops.members("set"); System.out.println(set); //移除:移除单个元素 ops.remove("set","jack"); //删除key redisTemplate.delete("set"); } }
// 右压栈
@Test
void test3() {
ListOperations<String, String> ops = redisTemplate.opsForList();
//右压栈
ops.rightPush("myList","a");
ops.rightPush("myList","b");
ops.rightPush("myList","c");
//取值:
List<String> myList = ops.range("myList", 0, -1);
System.out.println(myList);
}
// 左压栈
@Test
void test4() {
ListOperations<String, String> ops = redisTemplate.opsForList();
//左压栈
ops.leftPush("myList","A");
ops.leftPush("myList","B");
ops.leftPush("myList","C");
//取值:
List<String> myList = ops.range("myList", 0, -1);// CBAabc
System.out.println(myList);
}
// 根据索引查询元素
@Test
void test5() {
ListOperations<String, String> ops = redisTemplate.opsForList();
String value = ops.index("myList", 1);
System.out.println(value);
}
// 移除某个元素的值
@Test
void test6() {
ListOperations<String, String> ops = redisTemplate.opsForList();
ops.remove("myList",1,"A");
List<String> myList = ops.range("myList", 0, -1);//
System.out.println(myList);// CBabc
}
@Test
void test7() {
HashOperations<String, Object, Object> ops = redisTemplate.opsForHash();
// 存入值
ops.put("user","username","mrzhang");
ops.put("user","address","beijing");
ops.put("user","age","18");
}
// 提取所有的KEY
@Test
void test8() {
HashOperations<String, Object, Object> ops = redisTemplate.opsForHash();
// 提取所有的KEY[field]
Set<Object> user = ops.keys("user");
System.out.println(user);// username address age
}
// 提取所有的值
@Test
void test9() {
HashOperations<String, Object, Object> ops = redisTemplate.opsForHash();
// 提取所有的KEY[field]
List<Object> user = ops.values("user");
System.out.println(user);
}
// 根据KEY提取值
@Test
void test10() {
HashOperations<String, Object, Object> ops = redisTemplate.opsForHash();
// 提取所有的KEY[field]
String o = (String) ops.get("user", "username");
System.out.println(o);
}
// 根据KEY移除值
@Test
void test11() {
HashOperations<String, Object, Object> ops = redisTemplate.opsForHash();
// 提取所有的KEY[field]
ops.delete("user", "username");
}
RedisTemplate操作时,默认会采用jdkSerializable序列化机制,使得插入的值在redis客户端看来会有乱码,若想解决这一问题,需要手动指定序列化方式。
public class RedisTemplate<K, V> extends RedisAccessor implements
RedisOperations<K, V>, BeanClassLoaderAware {
public void afterPropertiesSet() {
super.afterPropertiesSet();
boolean defaultUsed = false;
if (this.defaultSerializer == null) {
this.defaultSerializer = new
JdkSerializationRedisSerializer(this.classLoader != null ? this.classLoader : this.getClass().getClassLoader());
}
...
}
/** * @author offcn * 实现序列化接口Serializable **/ public class Account implements Serializable { private Integer id; private String name; private double money; public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public double getMoney() { return money; } public void setMoney(double money) { this.money = money; } @Override public String toString() { return "Account{" + "id=" + id + ", name='" + name + '\'' + ", money=" + money + '}'; } }
/** * 指定自己的RedisTemplate 模板对象,模板对象设定给了序列化器 * @param redisConnectionFactory * @return 返回值装在在IOC容器当中 */ @Bean public RedisTemplate<String, Account> redisTemplate(RedisConnectionFactory redisConnectionFactory) { RedisTemplate<String, Account> template = new RedisTemplate(); template.setConnectionFactory(redisConnectionFactory); //创建一个序列化器: Jackson2JsonRedisSerializer serializer = new Jackson2JsonRedisSerializer(Account.class); //设置默认的序列化器 template.setDefaultSerializer(serializer); return template; }
@SpringBootTest public class RedisDemo02ApplicationTests { @Autowired private RedisTemplate<String,Account> redisTemplate; /** * 序列化对象到redis当中 */ @Test public void serializableAccount() { Account account = new Account(); account.setId(1001); account.setName("张三丰"); account.setId(98765); redisTemplate.opsForValue().set("account",account); System.out.println("序列化成功"); } }
点击跳转:https://blog.csdn.net/m0_67296957/article/details/130924125
<!--SpringBoot整合Mybatis的启动器--> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.1.4</version> </dependency> <!--mysql驱动包--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.47</version> </dependency> <!--web模块--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>
CREATE TABLE `account` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(255) DEFAULT NULL,
`money` double(10,2) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1005 DEFAULT CHARSET=utf8
public class Account implements Serializable { private Integer id; private String name; private double money; public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public double getMoney() { return money; } public void setMoney(double money) { this.money = money; } }
@Mapper
public interface AccountMapper {
Account findById(Integer id);
}
配置文件:
<?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="com.offcn.AccountMapper">
<!-- Account findById(Integer id);-->
<select id="findById" resultType="com.offcn.pojo.Account">
select * from acccont where id=#{id}
</select>
</mapper>
spring: datasource: url: jdbc:mysql://localhost:3306/test username: root password: root driver-class-name: com.mysql.jdbc.Driver # 配置mybatis规则 mybatis: #config-location: classpath:mybatis/mybatis-config.xml #全局配置文件位置 mapper-locations: classpath:mybatis/mapper/*.xml #sql映射文件位置 type-aliases-package: com.offcn.pojo #配置了实体的别名 configuration: map-underscore-to-camel-case: true #开启 驼峰式命名法 不写全局,局配置文件的配置都放在configuration对象当中 #在控制台打印sql语句 log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
业务层接口:
public interface AccountService {
Account findByIdService(Integer id);
}
实现类:
@Service
public class AccountServiceImpl implements AccountService {
@Autowired
private AccountMapper accountMapper;
@Override
public Account findByIdService(Integer id) {
return accountMapper.findById(id);
}
}
@Controller
@RequestMapping("account")
public class AccountController {
@Autowired
private AccountService accountService;
@GetMapping("findById")
@ResponseBody
public Account findById(Integer id){
return accountService.findByIdService(id);
}
}
访问地址:http://localhost:8080/account/findById?id=1001
测试结果:
点击跳转:https://blog.csdn.net/m0_67296957/article/details/131062288
分布式系统是若干个独立计算机的集合,这些计算机对于用户来说就像单个相关系统
分布式系统是建立在网络上的软件系统
(1)单一的应用架构
当网站流量很小时,只需一个应用,将所有功能都部署在一起,以减少部署节点和成本。此时用于简化增删改查工作量的数据访问框架(ORM)是关键。
适用于小型网站,小型管理系统,将所有功能都部署到一个功能里,简单易用。
缺点:1、性能扩展比较难
2、协同开发问题
3、不利于升级维护
(2)垂直的应用架构
当访问量逐渐增大,单一应用增加机器带来的加速度越来越小,将应用拆成互不相干的几个应用,以提升效率。此时,用于加速前端页面开发的Web框架(MVC)是关键。
通过切分业务来实现各个模块独立部署,降低了维护部署的难度,团队各司其职更易管理,性能扩展也更方便,更有针对性。
缺点: 公用模块无法重复利用,开发性的浪费
(3)SOA
当垂直应用越来越多,应用之间交互不可避免,将核心业务抽取出来,作为独立的服务,逐渐形成稳定的服务中心,使前端应用能更快速的响应多变的市场需求。此时用于提高业务复用及整合的分布式服务框架(RPC)是关键。
(4)流动计算架构
当服务越来越多,容量的评估,小服务资源的浪费等问题逐渐显现,此时需增加一个调度中心基于访问压力实时管理集群容量,提高集群利用率。此时,用于提高机器利用率的资源调度和治理中心(SOA)[ ServiceOriented Architecture]是关键。
RPC【Remote Procedure Call】是指远程过程调用,是一种进程间通信方式,他是一种技术的思想,而不是规范。它允许程序调用另一个地址空间(通常是共享网络的另一台机器上)的过程或函数,而不用程序员显式编码这个远程调用的细节。即程序员无论是调用本地的还是远程的函数,本质上编写的调用代码基本相同。
RPC基本原理如下:
Apache Dubbo (incubating) 是一款高性能、轻量级的开源Java RPC框架,它提供了三大核心能力:面向接口的远程方法调用,智能容错和负载均衡,以及服务自动注册和发现。
Dubbox 是一个分布式服务框架,其前身是阿里巴巴开源项目Dubbo ,被国内电商及互联网项目中使用,后期阿里巴巴停止了该项目的维护,当当网便在Dubbo基础上进行优化,并继续维护,为了与原有的Dubbo区分,故将其命名为Dubbox。
Dubbox 致力于提供高性能和透明化的RPC远程服务调用方案,以及SOA服务治理方案。简单的说,dubbox就是个服务框架,如果没有分布式的需求,其实是不需要用的,只有在分布式的时候,才有dubbox这样的分布式服务框架的需求,并且本质上是个服务调用的东东,说白了就是个远程服务调用的分布式框架。
官网:http://dubbo.apache.org/
官方文档:https://dubbo.apache.org/zh/docs/v2.7/
服务提供者(Provider):暴露服务的服务提供方,服务提供者在启动时,向注册中心注册自己提供的服务。
服务消费者(Consumer): 调用远程服务的服务消费方,服务消费者在启动时,向注册中心订阅自己所需的服务,服务消费者,从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,如果调用失败,再选另一台调用。
注册中心(Registry):注册中心返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者.
监控中心(Monitor):服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心
在Windows安装Zookeeper
网址:https://archive.apache.org/dist/zookeeper/zookeeper-3.6.2/
在解压的bin目录中打开命令窗口,运行zkServer.org/dist/zookeeper/zookeeper-3.6.2/
将conf下的zoo_sample.cfg复制一份改名为zoo.cfg即可。
注意几个重要的位置:
dataDir=./ 临时数据存储的目录(可写相对路径)
clientPort=2181 zookeeper的端口号
修改完成后再次启动zookeeper
ls / :列出zookeeper根下保存的所有节点
create -e /name"why":创建一个name节点,值为why
get /name:获取/name节点值
dubbo本身并不是一个服务软件。它其实就是一个jar包能够帮你的java程序连接到zookeeper,并利用zookeeper消费、提供服务。所以你不用在Linux上启动什么dubbo服务。但是为了让用户更好的管理监控众多的dubbo服务,官方提供了一个可视化的监控程序,不过这个监控即使不装也不影响使用。
下载路径:https://github.com/apache/dubbo-admin/tree/master
打开配置文件src\main\resources\application.properties,在其中指定Zookeeper
dubbo.registry.address=zookeeper://127.0.0.1:2181
在pom文件所在目录下打开命令窗口执行mvn clean package
在打包好的jar包路径下打开命令窗口执行:
java -jar dubbo-admin-0.0.1-SNAPSHOT.jar
打开浏览器输入localhost:7001/,登录用户名和密码均为root 进入首页。
dubbo-admin-2.6.1.war
由于这个软件是属于一个web应用,所以需要将这个文件存放到tomcat下的webapps下就可以,然后去启动
tomcat及其zookeeper.
注意: zookeeper3.6.0-bin: 中有一个管理的端口默认为8080,可以修改该端口号:
在 zoo.cfg配置文件中添加:admin.serverPort:9999
启动zookeeper
启动tomcat
通过浏览器,进行访问: http://192.168.150.133:8080/dubbo-admin-2.6.1
输入用户名与密码:
<packaging>pom</packaging> <dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>5.3.4</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>dubbo</artifactId> <version>2.6.1</version> </dependency> <dependency> <groupId>org.apache.zookeeper</groupId> <artifactId>zookeeper</artifactId> <version>3.6.0</version> </dependency> <!-- zookeeper的客户端 --> <dependency> <groupId>org.apache.curator</groupId> <artifactId>curator-framework</artifactId> <version>2.12.0</version> </dependency> </dependencies>
public class Customer implements Serializable{ private Integer cid; private String cname; private List<CustomerAddress> address; public Customer() { } public void setAddress(List<CustomerAddress> address) { this.address = address; } public Integer getCid() { return cid; } public void setCid(Integer cid) { this.cid = cid; } public String getCname() { return cname; } public void setCname(String cname) { this.cname = cname; } public List<CustomerAddress> getAddress() { return address; } }
package com.offcn.bean; import java.io.Serializable; public class CustomerAddress implements Serializable { private Integer caid; private String caDes; public CustomerAddress(Integer caid, String caDes) { this.caid = caid; this.caDes = caDes; } public CustomerAddress() { } public Integer getCaid() { return caid; } public void setCaid(Integer caid) { this.caid = caid; } public String getCaDes() { return caDes; } public void setCaDes(String caDes) { this.caDes = caDes; } @Override public String toString() { return "CustomerAddress{" + "caid=" + caid + ", caDes='" + caDes + '\'' + '}'; } }
package com.offcn.service;
import com.offcn.bean.CustomerAddress;
import java.util.List;
public interface CustomerService {
public List<CustomerAddress> showAllAddress(Integer cid);
}
package com.offcn.service;
import com.offcn.bean.CustomerAddress;
import java.util.List;
public interface OrderService {
public List<CustomerAddress> showAddressByCid(Integer cid);
}
(4)发布offcn_commons工程到本地仓库
执行 mvn install 命令
在dubbo父工程下,在创建一个子模块:offcn_service_provider
<dependency>
<groupId>com.offcn</groupId>
<artifactId>offcn_commons</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
public class CustomerServiceImpl implements CustomerService {
//根据用户id查询用户地址
@Override
public List<CustomerAddress> showAllAddress(Integer id) {
List<CustomerAddress> list = new ArrayList<>();
CustomerAddress customerAddress = new CustomerAddress(1,"五道口职业技术学院"); CustomerAddress customerAddress1 = new CustomerAddress(2,"圆明园职业技术学院"); list.add(customerAddress);
list.add(customerAddress1);
return list;
}
}
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dubbo="http://code.alibabatech.com/schema/dubbo" 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:应用名--> <dubbo:application name="offcn_service_provider"/> <!--指定注册中心:(zookeeper)--> <dubbo:registry address="zookeeper://192.168.150.133:2181"/> <!-- 用dubbo协议在20880端口暴露服务 --> <dubbo:protocol name="dubbo" port="20880" /> <!--声明要暴露的服务端口--> <!--interface:要暴露的接口类型; ref:接口对应的实现类--> <dubbo:service interface="com.offcn.service.CustomerService" ref="customerService"/> <!--实现类对象的定义--> <bean id="customerService" class="com.offcn.service.CustomerServiceImpl"> </bean> </beans>
<!--将spring(provider).xml做为当前应用的的上下文参数-->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:provider.xml</param-value>
</context-param>
<!--加载provider.xml,并进行监听-->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
在对应的pom文件中添加db_commons的依赖
<dependency>
<groupId>com.offcn</groupId>
<artifactId>offcn_commons</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
@Service
public class OrderServiceImpl implements OrderService {
@Resource
private CustomerService customerService;
@Override
public List<CustomerAddress> showAddressByCid(Integer cid) {
return customerService.showAllAddress(cid);
}
}
@Controller
public class OrderController {
@Resource
private OrderService orderService;
@RequestMapping("/showInfo")
public String showInfo(){
List<CustomerAddress> customerAddresses = orderService.showAddressByCid(1);
System.out.println(customerAddresses);
return "show";
}
}
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dubbo="http://code.alibabatech.com/schema/dubbo" xmlns:bubbo="http://code.alibabatech.com/schema/dubbo" 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"> <!--扫描service注解--> <context:component-scan base-package="com.offcn.service"/> <!--dubbo:应用名--> <dubbo:application name="offcn_service_constumer"/> <!--指定注册中心:(zookeeper)--> <dubbo:registry address="zookeeper://192.168.150.133:2181"/> <!--远程调用--> <bubbo:reference interface="com.offcn.service.CustomerService" id="customerService"/> </beans>
<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" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd"> <context:component-scan base-package="com.offcn.controller"/> <mvc:annotation-driven/> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/"></property> <property name="suffix" value=".jsp"></property> </bean> </beans>
<context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:customer.xml</param-value> </context-param> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <servlet> <servlet-name>springmvc</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:springmvc.xml</param-value> </init-param> </servlet> <servlet-mapping> <servlet-name>springmvc</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping>
show.jsp
<body>
<h2>展示数据</h2>
</body>
在集群负载均衡时,Dubbo提供了多种均衡策略。
随机,按权重设置随机概率
在一个截面上碰撞的概率高,但调用量越大分布越均匀,而且按概率使用权重后也比较均匀,有利于动态调整提供者权重。
轮循,按公约后的权重设置轮循比率
存在慢的提供者累积请求的问题,比如:第二台机器很慢,但没挂,当请求调到第二台时就卡在那,久而久之,所有请求都卡在调到第二台上。
最少活跃调用数,相同活跃数的随机,活跃数指调用前后计数差。
使慢的提供者收到更少请求,因为越慢的提供者的调用前后计数差会越大。
一致性 Hash,相同参数的请求总是发到同一提供者。
当某一台提供者挂时,原本发往该提供者的请求,基于虚拟节点,平摊到其它提供者,不会引起剧烈变动。
当服务器压力剧增的情况下,根据实际业务情况及流量,对一些服务和页面有策略的不处理或换种简单的方式处理,从而释放服务器资源以保证核心交易正常运作或高效运作。
可以通过服务降级功能临时屏蔽某个出错的非关键服务,并定义降级后的返回策略。
向注册中心写入动态配置覆盖规则:
RegistryFactory registryFactory = ExtensionLoader.getExtensionLoader(RegistryFactory.class).getAdaptiveExtension();
Registry registry = registryFactory.getRegistry(URL.valueOf("zookeeper://10.20.153.10:2181"));
registry.register(URL.valueOf("override://0.0.0.0/com.foo.BarService?category=configurators&dynamic=false&application=foo&mock=force:return+null"));
其中:
1、mock=force.mocked 表示消费方对该服务的方法调用都直接返回 null 值,不发起远程调用.
2、还可以改为 mock=fail.mocked 表示消费方对该服务的方法调用在失败后,再返回 null 值,不抛异常。
在集群调用失败时,Dubbo 提供了多种容错方案,缺省为 failover 重试。
集群容错模式
失败自动切换,当出现失败,重试其它服务器。通常用于读操作,但重试会带来更长延迟。可通过retries=“2” 来设置重试次数(不含第一次)。
重试次数配置如下:
<dubbo:service retries="2" />
或
<dubbo:reference retries="2" />
或
<dubbo:reference>
<dubbo:method name="findFoo" retries="2" />
</dubbo:reference>
快速失败,只发起一次调用,失败立即报错。通常用于非幂等性的写操作,比如新增记录
失败安全,出现异常时,直接忽略。通常用于写入审计日志等操作。
失败自动恢复,后台记录失败请求,定时重发。通常用于消息通知操作。
并行调用多个服务器,只要一个成功即返回。通常用于实时性要求较高的读操作,但需要浪费更多服务资源。
可通过 forks=“2” 来设置最大并行数。
广播调用所有提供者,逐个调用,任意一台报错则报错 [2]。通常用于通知所有提供者更新缓存或日志等本地资源信息。
集群模式配置
按照以下示例在服务提供方和消费方配置集群模式
<dubbo:service cluster=“failsafe” />
或
<dubbo:reference cluster=“failsafe” />
点击跳转:https://blog.csdn.net/m0_67296957/article/details/131068149
Zookeeper是一个开源的分布式的,为分布式应用提供协调服务的Apache项目。
它是一种集中式服务,用于维护配置信息,命名,提供分布式同步和提供组服务。所有这些类型的服务都以分布式应用程序的某种形式使用。每次实施它们都需要做很多工作来修复不可避免的错误和竞争条件。由于难以实现这些类型的服务,应用程序最初通常会吝啬它们,这使得它们在变化的情况下变得脆弱并且难以管理。即使正确完成,这些服务的不同实现也会在部署应用程序时导致管理复杂性。
基于观察者,模式设计的分布式服务管理框架。
官方架构图:
特点:
Zookeeper数据模型的结构与Unix文件系统很类似,都是树结构。树上有若干个节点,每个节点能够存储1MB的数据,同时每个节点都是通过其路径可以唯一标识的。
Zookeeper服务包括::统一命名服务、统一配置管理、统一集群管理、软负载均衡等。
命名服务是分布式系统中较为常见的一类场景,分布式系统中,被命名的实体通常可以是集群中的机器、提供的服务地址或远程对象等。通过命名服务,客户端可以根据指定名字来获取资源的实体、服务地址和提供者信息。例如一般用户都是通过域名来访问应用而不是IP。阿里开源分布式服务框架Dubbo中使用zookeeper来作为其命名服务,维护全局的服务列表。
(1)配置文件同步,集群中所有配置文件的信息都是一致的,对配置文件修改后,快速同步到各个节点上。
(2)Zookeeper实现配置管理。将配置信息写入到Zookeeper上的节点,然后各个客户端服务器监听这个节点,一但节点中的数据发生变化,Zookeeper将通知到各个客户端服务器。
zookeeper的两大特性:节点特性和watcher机制
1、分布式环境中实时掌握每个节点的状态,并根据节点实时状态做出一定的调整。
2、在实时监测到节点变化后,将节点的信息写入到Zookeeper上的节点,通过监听该节点来获取它的实时状态变化。
zookeeperk实现负载均衡就是通过watcher机制和临时节点判断哪些节点宕机来获取可用的节点来实现的。zookeeperk会维护一个树形的数据结构,类似于window的资源管理器目录,其中 EPHEMERAL(临时)节点会随着创建它的客户端端口而被删除,利用这个特性很容易实现软负载均衡。
下载地址:https://zookeeper.apache.org/releases.html#download
将/usr/apache-zookeeper-3.6.0-bin/conf这个路径下的zoo_sample.cfg修改为zoo.cfg;
mv zoo_sample.cfg zoo.cfg
在/usr/apache-zookeeper-3.6.0-bin这个目录上创建zkData文件夹
mkdir zkData
打开zoo.cfg文件,修改dataDir路径:
vi zoo.cfg
修改如下内容:
dataDir=/usr/apache-zookeeper-3.6.0-bin/zkData
在/usr/apache-zookeeper-3.6.0-bin目录下执行: ./bin/zkServer.sh start
**./bin/zkServer.sh status **:查看启动状态,未正常启动,到logs/下查看日志文件,
如果是因为端口被占用导致未正常启动:
解决方案:
修改conf/zoo.cfg
admin.serverPort=未被使用的端口,比如:9999
4020 Jps
4001 QuorumPeerMain
在/usr/apache-zookeeper-3.6.0-bin目录下执行: ./bin/zkServer.sh statu
显示状态:
ZooKeeper JMX enabled by default
Using config: /opt/apache-zookeeper-3.6.0-bin/bin/…/conf/zoo.cfg
Mode: standalone
在/usr/apache-zookeeper-3.6.0-bin目录下执行:./bin/zkCli.sh
在/usr/apache-zookeeper-3.6.0-bin目录下执行:bin/zkServer.sh stop
Zookeeper中的配置文件zoo.cfg中参数含义解读如下:
Zookeeper服务器与客户端心跳时间,单位毫秒
Zookeeper使用的基本时间,服务器之间或客户端与服务器之间维持心跳的时间间隔,也就是每个tickTime时间就会发送一个心跳,时间单位为毫秒。
它用于心跳机制,并且设置最小的session超时时间为两倍心跳时间。(session的最小超时时间是2*tickTime)
集群中的Follower跟随者服务器与Leader领导者服务器之间初始连接时能容忍的最多心跳数(tickTime的数量),用它来限定集群中的Zookeeper服务器连接到Leader的时限。
集群中Leader与Follower之间的最大响应时间单位,假如响应超过syncLimit * tickTime,Leader认为Follwer死掉,从服务器列表中删除Follwer。
主要用于保存Zookeeper中的数据。
监听客户端连接的端口。
(1)半数机制:集群中半数以上机器存活,集群可用。所以Zookeeper适合安装奇数台服务器。
(2)Zookeeper虽然在配置文件中并没有指定Master和Slave。但是,Zookeeper工作时,是有一个节点为Leader,其他则为Follower,Leader是通过内部的选举机制临时产生的。
(3)以一个简单的例子来说明整个选举的过程。
假设有五台服务器组成的Zookeeper集群,它们的id从1-5,同时它们都是最新启动的,也就是没有历史数据,在存放数据量这一点上,都是一样的。假设这些服务器依序启动,来看看会发生什么,如图所示。
图中Zookeeper的选举机制
(1)服务器1启动,此时只有它一台服务器启动了,它发出去的报文没有任何响应,所以它的选举状态一直是
LOOKING状态。
(2)服务器2启动,它与最开始启动的服务器1进行通信,互相交换自己的选举结果,由于两者都没有历史数
据,所以id值较大的服务器2胜出,但是由于没有达到超过半数以上的服务器都同意选举它(这个例子中的半数
以上是3),所以服务器1、2还是继续保持LOOKING状态。
(3)服务器3启动,根据前面的理论分析,服务器3成为服务器1、2、3中的老大,而与上面不同的是,此时有
三台服务器选举了它,所以它成为了这次选举的Leader。
(4)服务器4启动,根据前面的分析,理论上服务器4应该是服务器1、2、3、4中最大的,但是由于前面已经
有半数以上的服务器选举了服务器3,所以它只能接收当小弟的命了。
(5)服务器5启动,同4一样当小弟。
1、持久节点(Persistent):服务端和客户端断开连接后,创建的节点不删除;
持久化目录节点:服务端和客户端断开连接后,该节点仍然存在;
持久化顺序编号目录节点:服务端和客户端断开连接后,该节点仍然存在;只是zook给该节点名称进行顺序编号。
2、短暂节点(Ephemeral):服务端和客户端断开连接后,创建的节点自己删除;
临时目录节点:客户端与Zookeeper断开连接后,该节点被删除
临时顺序编号目录节点:客户端与Zookeeper断开连接后,该节点被删除,只是zook给该节点名称进行顺序编号。
以3台服务器的Zookeeper集群为例,一个Leader,两个Follower(server1和server2)。
(1)Client向Zookeeper的server1发送一个写请求,客户端写数据到服务器1上;
(2)如果server1不是Leader,那么server1会把接收到的写请求转发给Leader;然后 Leader会将写请求转发 给每个server;
server1和server2负责写数据,并且两个Follower的写入数据是一致的,保存相同的数据副本;
server1和server2写数据成功后,通知Leader;
(3)当Leader收到集群半数以上的节点写成功的消息后,说明该写操作执行成功;
例如:这里是3台服务器,只要2台Follower服务器写成功就ok
因为client访问的是server1,所以Leader会告知server1集群中数据写成功;
(4)被访问的server1进一步通知client数据写成功,这时,客户端就知道整个写操作成功了。
相比写数据流程,读数据流程就简单得多;因为每台server中数据一致性都一样,所以随便访问哪台server 读数据就行;没有写数据流程中请求转发、数据同步、成功通知这些步骤。
在zk01、zk02和zk03三个节点上部署Zookeeper。
Leader服务器使整个Zookeeper集群的核心,主要的工作任务有两项:
Follower角色的主要职责是:
Observer 是 zookeeper3.3 开始引入的一个全新的服务器 角色,从字面来理解,该角色充当了观察者的角色。 观察zookeeper集群中的最新状态变化并将这些状态变化 同步到 observer 服务器上。Observer的工作原理与follower角色基本一致,而它和follower角色唯一的不同在于observer 不参与任何形式的投票,包括事务请求 Proposal的投票和leader选举的投票。简单来说,observer服务器只提供非事务请求服务,通常在于不影响集群事务 处理能力的前下提升集群非事务处理的能力.
(1)解压Zookeeper安装包到/usr/目录下
tar -zxvf apache-zookeeper-3.6.0-bin.tar.gz -C /usr/
(2)拷贝/usr/apache-zookeeper-3.6.0-bin目录内容到另外两台服务器。
(1)在/usr/apache-zookeeper-3.6.0-bin这个目录下创建zkData
mkdir -p zkData
(2)在/usr/apache-zookeeper-3.6.0-bin/zkData目录下创建一个myid的文件
touch myid
添加myid文件,注意一定要在linux里面创建,在notepad++里面很可能乱码
(3)编辑myid文件
vi myid
在文件中添加与server对应的编号1:
(4)在其他两台服务器上分别创建zkData目录,并在目录中创建myid文件,myid文件中内容为2,3。
(1)在三台服务器上重命名/usr/apache-zookeeper-3.6.0-bin/conf这个目录下的zoo_sample.cfg为zoo.cfg
mv zoo_sample.cfg zoo.cfg
(2)打开zoo.cfg文件
vi zoo.cfg
修改数据存储路径配置
dataDir=/usr/apache-zookeeper-3.6.0-bin/zkData
增加如下配置
#######################cluster##########################
server.1=192.168.164.136:2888:3888
server.2=192.168.164.137:2888:3888
server.3=192.168.164.138:2888:3888
(3)其他两台服务器同样完成以上两步关于zoo.cfg配置文件的操作
(1)分别启动Zookeeper
bin/zkServer.sh start
(2)查看状态
bin/zkServer.sh status
1.启动客户端 bin /zkCli.sh 2.显示所有操作命令 help 3.查看当前znode中所包含的内容 ls / 4.查看当前节点详细数据 ls –s / 5.分别创建2个普通节点 create /shuihu "songjiang" create /shuihu/liangshan "liubei" 6.获得节点的值 get /shuihu 7.创建短暂节点 create -e /shuihu/liangshan "likui" (1)在当前客户端是能查看到的 ls /shuihu (2)退出当前客户端然后再重启客户端 quit:退出 bin/zkCli.sh重新进入 (3)再次查看根目录下短暂节点已经删除 ls /shuihu 8.创建带序号的节点 (1)先创建一个普通的根节点/shuihu/liangshan create /shuihu/liangshan/daxia "yanqing" Created /shuihu/liangshan/daxia (2)创建带序号的节点 create -s /shuihu/laingshan/yishi "lujunyi" 如果原来没有序号节点,序号从0开始依次递增。如果原节点下已有2个节点,则再排序时从2开始,以此类推。 9.修改节点数据值 set /shuihu/liangshan "lishishi" 10.节点的值变化监听 (1)在主机centos_test上注册监听/shuihu节点数据变化 get /shuhui -w (2)在centos_test2主机上修改/shuihu节点的数据 set /shuihu "huangdi" (3)观察centos_test主机收到数据变化的监听 WATCHER:: WatchedEvent state:SyncConnected type:NodeDataChanged path:/shuihu 11.节点的子节点变化监听(路径变化) (1)在centos_test主机上注册监听/liangshan节点的子节点变化 ls /liangshan -w (2)在centos_test2主机/liangshan节点上创建子节点 create /liangshan/chaoting "huarong" (3)观察centos_test主机收到子节点变化的监听 WATCHER:: WatchedEvent state:SyncConnected type:NodeChildrenChanged path:/shuihu 12.删除节点 delete /shuihu/chaoting 13.递归删除节点 deleteall /shuihu/liangshan 14.查看节点状态 stat /shuihu
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.6.0</version>
</dependency>
</dependencies>
需要在项目的src/main/resources目录下,新建一个文件,命名为“log4j.properties”,在文件中填入
log4j.rootLogger=INFO, stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - %m%n
log4j.appender.logfile=org.apache.log4j.FileAppender
log4j.appender.logfile.File=target/spring.log
log4j.appender.logfile.layout=org.apache.log4j.PatternLayout
log4j.appender.logfile.layout.ConversionPattern=%d %p [%c] - %m%n
private static String connectString = "192.168.164.136:2181,192.168.164.137:2181,192.168.164.138:2181"; private static int sessionTimeout = 60000; private ZooKeeper zkClient = null; @Before public void init() throws Exception { zkClient = new ZooKeeper(connectString, sessionTimeout, new Watcher(){ public void process(WatchedEvent event) { // 收到事件通知后的回调函数(用户的业务逻辑) System.out.println(event.getType() + "--" + event.getPath()); // 再次启动监听 try { zkClient.getChildren("/", true); } catch (Exception e) { e.printStackTrace(); } } }); }
// 创建子节点
@Test
public void create(){
// 参数1:要创建的节点的路径; 参数2:节点数据 ; 参数3:节点权限 ;参数4:节点的类型
try {
String nodeCreated = zkClient.create("/offcn", "youjiuye".getBytes(),ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
} catch (KeeperException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 获取子节点
@Test
public void getChildren() throws Exception {
List<String> children = zkClient.getChildren("/", true);
for (String child : children) {
System.out.println(child);
}
// 延时阻塞
Thread.sleep(Long.MAX_VALUE);
}
// 判断znode是否存在
@Test
public void exist() throws Exception {
Stat stat = zkClient.exists("/shuihu", false);
System.out.println(stat == null ? "not exist" : "exist");
}
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。