一、知识梳理
- 用户的所以请求首先通过api网关,Zuul。Zuul在我们的项目中主要用来转发请求链接。转发到内网中服务器,起到反向代理的作用,保证内网服务器不允许外部用户直接访问。网络比较安全。Zuul在application.yml文件中配置映射。
- 用户请求就被转向业务消费者,消费者进过feign封装,更好的支持REST形式访问。具体我们做了一个接口,用来封装操作。UserFeign,也支持Ribbon,经过Ribbon本地动态网址,可以快速直接访问服务调用者,还可实现负载均衡,类似Nginx。
- 经过Hystrix封装,整个架构也具有断路器功能,当业务访问正常时,断路器不工作,他的状态是“关闭”的,当业务失败时,当失败此时超过阈值,断路器开始工作,他的状态“打开”,但不会一直处于打开状态,每次调用都会先判断业务是否正常。Hystrix检查动态服务列表能否正常访问。
- Eureka基于AP,访问频率达不到阈值60每秒,启动保护模式,不会动态维护列表。不正确,快速失效,回调配置断路器方法。
- 方法自行处理,常用:①设置默认price②返回通用对象,json业务消费者经过层层封装调用后,访问业务提供者,他对外暴露RESTFul请求+json。业务提供者可以注入@Value属性值,但是这个值为了支持分布式环境的配置,引入配置中心,代替传统属性文件。要维护属性和属性的动态变化,配置中心就引入GIT,通过GIT来维护属性的值。
为什么引入配置中心:
微服务会管理很多服务器或集群,为了同时更新配置,还可以动态刷新最新的配置。
在实际大型项目中,如果并发量非常大,还需要对这个架构继续升级:
1)api网关可进行高可用
2)Eureka注册中心高可用
3)服务的提供者,采用负载均衡
4)Config-server配置中心高可用
GIT
进行通信,维护代码:
1、pull,每天一上班,把远程仓库的内容拉取到本地
2、add,可以一个代码文件写完,写完一段完整的业务,add
3、commit,上午提交,下班之前提交;一般来说add+commit
4、push,一个模块的代码全部完成,下班之前提交。
为什么每天下班之前要提交一次?集成测试,不同人写的代码在一起是否能运行。
GIT与SVN差异?
1、结构,SVN中央集权结构,GIT分散,分布式。
2、管理侧重点不同,SVN侧重管理相关文档,GIT侧重管理代码。
3、权限管理,SVN就可以根据配置的权限,限定用户的浏览下载权限,文档或者代码就非常安全。GIT目标:共享,在权限管理上非常弱。
配置中心
1、传统属性文件,当服务多了时,要修改属性时,很难同时修改大量机器上的属性文件,在短时间内属性值不同步。
配置中心单独服务器,单独集群
2、属性不能动态更新,需要重启服务器。
-注解@RefreshScope,支持手动刷新,bootstrap.yml/properties(它比@Value注解先执行,比application.yml/properties先执行。)关闭安全配置false,执行手动POST的刷新情况。
-SpringCloud-BUS总线,基于RabbitMQ。异步+MQ+并发,发布订阅。
二、MyBatisPlus
今日任务:
1)整体技术体系,实现user表的CURD操作,所有技术一起使用
2)新的技术,通用Mapper,实现单表CRED操作SQL不用谢,代替工具,升级版本MybatisPlus
MyBatisPlus
使用SpringBoot + Mybatis + MybatisPlus(MP) 全新注解方式,自动产生SQL语句,替代旧的通用Mapper。旧的通用Mapper是基于Mybatis拦截器的机制,而新的MybatisPlus是基于注解扫描机制,在启动服务时就进行加载,所以性能比旧的通用Mapper方式高很多。
三、Provider-User-Demo
1、画图
2、准备数据库
3、新建项目组
拷贝Zuul和EurekaServer
3、新建提供者项目ProviderUser
4、pom依赖
<!-- 父依赖 -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.4.RELEASE</version>
<relativePath />
</parent>
<!-- 设定jdk版本,字符编码,SpringCloud版本地名 -->
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<spring-cloud.version>Dalston.SR1</spring-cloud.version>
</properties>
<dependencies>
<!-- eureka配置中心 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
<!-- web服务 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- mybatisplus/mybatis/jdbc/druid -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.0</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!-- mybatisplus与springboot整合 -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatisplus-spring-boot-starter</artifactId>
<version>1.0.5</version>
</dependency>
<!-- MP 核心库 -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus</artifactId>
<version>2.1.8</version>
</dependency>
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>1.2.2</version>
</dependency>
<!-- alibaba的druid数据库连接池 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.0</version>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
</project>
5、POJO对象
要注意数据库表创建时主键id是自增的。
省略getter、setter方法。
//表和类进行映射
@TableName("user")
public class User implements Serializable{
// id name birthday address
// 配置主键,主键自增策略
@TableId(type=IdType.AUTO)
private Integer id;
// 映射,全局配置驼峰规则,MybatisPlus自动修改,不需要再改为user_Name或userName
private String name;
@DateTimeFormat(pattern="yyyy-MM-dd")
private Date birthday;
private String address;
6、mapper 错误原因:没有写User泛型
package com.zz.mapper;
import com.baomidou.mybatisplus.mapper.BaseMapper;
import com.zz.pojo.User;
public interface UserMapper extends BaseMapper<User>{
}
7、service
public interface UserService {
public List<User> findAll();
}
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserMapper userMapper;
// 查看用户列表
public List<User> findAll() {
return userMapper.selectList(null);
}
}
8、controller
@RestController
@RequestMapping("/user")
public class UserController {
// 我要显示所有的用户 是跟service做联系的
@Autowired
private UserService userService;
@RequestMapping("/findAll")
public List<User> findAll() {
return userService.findAll();
}
}
9、创建启动类
@SpringBootApplication
@MapperScan("com.zz.mapper")//mapper扫描包路径
@EnableEurekaClient
public class RunAppProviderUser {
public static void main(String[] args) {
SpringApplication.run(RunAppProviderUser.class, args);
}
}
10、配置yml文件
server:
port: 7900
spring:
application:
name: Provider-User
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/mybatisdb
username: root
password:
eureka:
client:
serviceUrl:
defaultZone: http://user:password123@localhost:8761/eureka
mybatis:
mapUnderscoreToCamelCase: true
typeAliasesPackage: cn.zz.pojo
mapperLocations: classpath:mappers/*.xml
mybatis-plus:
configuration:
map-underscore-to-camel-case: true
ribbon:
eureka:
enabled: true
logging:
level:
cn.jt.mapper: debug
运行~
太丑了,Chrome中安装jsonview插件,遇到问题
将crx后缀替换为.rar
将rar解压,将“_metadata”改名为"metadata"
将解压的文件拖拽上去,安装完成啦
剩余步骤,添加、修改和删除
1、mapper不用动,因为继承了BaseMapper
2、Service、ServiceImpl
// 新增
public Integer insert(User entity);
// 修改
public Integer update(User entity);
// 批量删除 mybatisplus自带批量功能
/**
* <p>
* 删除(根据ID 批量删除)
* </p>
*
* @param idList 主键ID列表
* @return int
*/
public void delete(List<Integer> ids);
// 新增
public Integer insert(User entity) {
// TODO Auto-generated method stub
return userMapper.insert(entity);
}
// 修改
public Integer update(User entity) {
// TODO Auto-generated method stub
return userMapper.updateById(entity);
}
// 批量删除
public void delete(List<Integer> ids) {
// TODO Auto-generated method stub
userMapper.deleteBatchIds(ids);
}
3、Controller
// 新增
@RequestMapping("/insert/{name}/{birthday}/{address}")
public String insert(User user) {
try {
int row = userService.insert(user);
return "insert item:"+row+"~";
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
return "insert failed";
}
}
// 修改
@RequestMapping("/update/{id}/{name}/{birthdya}/{address}")
public String update(User user) {
try {
int row = userService.update(user);
return "update item:"+row+"~";
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
return "update failed";
}
}
// 批量删除
@RequestMapping("/delete/{id}")
public String delete(@PathVariable Integer id) {
try {
List<Integer> ids = new ArrayList<Integer>();
ids.add(id);
userService.delete(ids);
return "delete success";
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
return "delete failed";
}
}
来试一下吧~
模糊查询:
1、service、serviceImpl
// 按条件查询
public List<User> findUser(User user);
// 按姓名进行条件查询
public List<User> findUser(User user) {
// 封装where条件
EntityWrapper wrapper = new EntityWrapper();
// 封装对象里面写的都是数据库的字段名称
wrapper.like("name", user.getName());//QBC面向对象
return userMapper.selectList(wrapper);
}
2、controller
// 条件查询
@RequestMapping("/find/{name}")
public List<User> find(User user) {
return userService.findUser(user);
}
3、运行一下吧~
提供者配置完成!
四、Provider-Client-Demo
实现服务消费者
1)调用服务提供者,ribbon,feign接口(他接口写法和springmvc controller不一致,有很多的坑)
2)最著名的坑,日期。birthday不能直接转换,springmvc
json转换工具类objectMapper,对本地日期有时差(8小时),算完后,写一个拦截器,去完成增加8小时。Feign接参时,,全用字符串。
Http协议中没有数据类型,String,request.getParamter(“name”);
3)Feign不支持多级的@Requestmapping
4)Feign不能直接接受对象,很多参数post+ajax,参数以json提交。
5)加入Hystrix,和Zuul集成,在Zuul来具体调用断路器方法。
1、pom文件配置
<!-- 父依赖 -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.4.RELEASE</version>
<relativePath />
</parent>
<!-- 过滤字符编码 设定java版本 制定SpringCloud版本地名 -->
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<spring-cloud.version>Dalston.SR1</spring-cloud.version>
</properties>
<dependencies>
<!-- eureka注册中心依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
<!-- Hystrix熔断器 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix</artifactId>
</dependency>
<!-- RESTFul -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-feign</artifactId>
</dependency>
<!-- web场景启动器 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<!-- SpringCloud项目管理依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
2、UserFeign和POJO-User
新建UserFeign,把Provider中的Controller的方法复制过来,发现需要User对象,所以把POJO类拷过来,去掉其与MybatisPlus相关联的注解。
此处的更改,在Consumer,所以对应的provider也要保持同步!!!
在方法上方的注解有一个常用的@ResponseBody,现在有在方法传参位置使用的@RequestBody【未使用】
Feign:
@FeignClient("Provider-User")
public interface UserFeign {
@RequestMapping("/user/findAll") //必须路径写全
public List<User> findAll(); //POJO中的日期的处理
@RequestMapping("/user/insert/{name}/{birthday}/{address}") //对象传参,内部走的是json提交
public String insert(@PathVariable("name") String name,
@PathVariable("birthday") String birthday,
@PathVariable("address") String address);
@RequestMapping("/user/update/{id}/{name}/{birthday}/{address}")
public String update(
@PathVariable("id") Integer id,
@PathVariable("name") String name,
@PathVariable("birthday") String birthday,
@PathVariable("address") String address);
@RequestMapping("/user/delete/{id}")
public String delete(@PathVariable("id") Integer id);
@RequestMapping("/user/find/{name}")
public List<User> find(@PathVariable("name") String name);
4、Controller
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UserFeign userFeign;
// 查找全部
@RequestMapping("/findAll") //必须路径写全
public List<User> findAll(){
return userFeign.findAll();
}
// 模糊查找
@RequestMapping("/find/{name}")
public List<User> find(User user) {
return userFeign.find(user.getName());
}
// 插入
@RequestMapping("/insert/{name}/{birthday}/{address}")
public String insert(User user){
return userFeign.insert(user.getName(), user.getBirthday(), user.getAddress());
}
// 更改
@RequestMapping("/update/{id}/{name}/{birthday}/{address}")
public String update(User user) {
return userFeign.update(user.getId(),user.getName(),user.getBirthday(),user.getAddress());
}
//按id批量删除
@RequestMapping("/delete/{id}")
public String delete(@PathVariable Integer id){
return userFeign.delete(id);
}
}
5、配置yaml文件
server:
port: 9001
spring:
application:
name: ConsumerUser
eureka:
client:
serviceUrl:
defaultZone: http://user:password123@localhost:8761/eureka
logging:
level:
root: INFO
6、配置启动类
@SpringCloudApplication//使用SpringCloud管理项目架构
@EnableFeignClients//支持Feign的Client用户端
@EnableCircuitBreaker//启动Hystrix
public class RunAppConsumerUser {
public static void main(String[] args) {
// TODO Auto-generated method stub
SpringApplication.run(RunAppConsumerUser.class, args);
}
}
配置完成!
首先要启动Eureka,然后启动服务端,最后启动消费者测试一下吧!
错点:为什么服务提供者没有在Eureka注册中心显示?因为ProviderUser的启动类中没有添加注解@EnableEurekaClient!!!
使用访问Client端实现:查找全部。
发现问题:日期显示不正确。
顺便也把provider-user的pojo也改了。对应起来
引入Ribbon进行负载均衡配置:
1)直接调动RestTemplate.getForObject():
2)@Bean@LoadBlance
3)Ribbon客户端,实现负载均衡配置
在提供者application.yml配置下面语句,启用Ribbon:
ribbon:
eureka:
enabled: true
模糊查找:
添加:
修改:
删除:
总结错误原因:
provider和consumer对应的方法的传参、注解 都要保持一致!!!
provider和consumer对应的方法的传参、注解 都要保持一致!!!
provider和consumer对应的方法的传参、注解 都要保持一致!!!
配置检验网关
1、pom
2、yaml
server:
port: 8050
spring:
application:
name: gateway-zuul
eureka:
client:
serviceUrl:
defaultZone: http://user:password123@localhost:8761/eureka
zuul:
ignoredServices: '*'
routes:
app-provider-user:
path: /user/**
serviceId: Provider-User
3、FallBack
//未被实例化,可通过包扫描加载。但是既不属于Controller,也不属于Service 所以使用@Component
@Component
public class ZullFallBack implements ZuulFallbackProvider {
// 获取路由 application.name-->Provider-User :provider-user ,同yaml文件配置。
public String getRoute() {
// TODO Auto-generated method stub
return "Provider-User";
}
// 设置返回值,通常用Json体现,utf-8防止中文乱码
public ClientHttpResponse fallbackResponse() {
// TODO Auto-generated method stub
return new ClientHttpResponse() {//匿名内部类
// 请求响应头信息 contentType和字符类型
public HttpHeaders getHeaders() {
// 返回类型为Json,设置字符集为UTF-8
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON_UTF8);
return headers;
}
// 响应体,具体返回内容,就是Hystrix断路器设定,断路时显示的默认内容
public InputStream getBody() throws IOException {
String defaultValue = "Default Value From Zuul!";//标准应为Json字符串
return new ByteArrayInputStream(defaultValue.getBytes());
}
// 返回文字描述====固定内容
public String getStatusText() throws IOException {
// TODO Auto-generated method stub
return HttpStatus.BAD_REQUEST.getReasonPhrase();
}
// 返回状态码
public HttpStatus getStatusCode() throws IOException {
// TODO Auto-generated method stub
return HttpStatus.BAD_REQUEST;
}
// 返回二进制状态码
public int getRawStatusCode() throws IOException {
// 可不写
return 0;
}
// 关闭释放资源
public void close() {
// 可不写
}
};
}
}
4、启动类
@SpringCloudApplication //项目架构
@EnableZuulProxy //标志API网关
@EnableEurekaClient //Eureka客户端
public class RunApplicationZuul {
public static void main(String[] args) {
// TODO Auto-generated method stub
SpringApplication.run(RunApplicationZuul.class, args);
}
}
运行:
1、启动Eureka
2、启动提供者1和2
3、启动消费者
4、启动zuul网关配置
访问zuul:localhost:8050/user/user/***
网关、负载均衡配置成功。负载均衡Ribbon是配置在两个提供者的yaml上的。
加了Zuul有了什么变化?
1、fallback,写在zuul
2、如果直接访问消费者,Feign @RequestBody,httpclientPost请求就可以
3、如果访问Zuul,调用消费者Feign @RequestBody,httpclientJson请求
注意问题:断路器没生效
①可能是因为 没有在Consumer的入口类中添加@EnableCircuitBreaker//启动Hystrix,支持Hystrix断容器,导致访问zuul停掉其中一个provider时候出现显示异常。
②Zuul自带断路器。它的FallBack实现ZuulFallbackProvider接口,获取路由是提供者的服务名称。
总结:
一、
1、Eureka+Provider1+Provider2+Consumer(Feign+Hystrix+Ribbon)+Zull(FallBack)
2、Eureka没有变化,直接使用【版本号不同】。
3、User1,对象传参,REST日期(消费者和提供者,日期类型字符串,保持一致),对象传参,Feign支持不好,改成json传参,变相对象传参@RequestBody【不会用暂时】
4、User1和User2,支持一个业务多个通道,提高并发,必须有区别:端口号不同。
application.name必须重复,Eureka会按名称分到一组,Ribbon按组来轮询。
5、Ribbon负载均衡配置在Provider的yaml上,通过Consumer来调用或者Zuul来调用。如果对象的传参Feign 中引用@ResponseBody。如果直接访问消费者,以post请求,不能get请求。如果通过zuul访问,必须以json方式。
6、Zuul,要修改pom文件,设定断路器的“fallback”的路由【provider的application-name】。
二、
1、服务框架Zuul直接调用提供者
2、Zuul+Consumer消费者,经过FeignRESTFul支持,Ribbon负载均衡
3、拦截器,影响性能。Feign不够了解,掌握一般用法。
4、全部整合起来,消费者调用Feign,Feign支持不够好,问题很多:
1)@FeignClien(“Provider-User”) 调用的是服务提供者
2)接口中方法,代表提供者,直接仿照提供者,高度一致
3)Controller支持@RequestMapping多级。但是Feign不支持多级的@RequestMapping
4)Feign中的@PathVariable的映射名每一个都要写
5)对象参数SpringCloud(RESTFul+Json)支持json传参,SpringCloud内部根据@RequestBody,把获取json强制转换为user java对象
6)请求方式如果直接调用消费者,使用htpclient模拟post请求;如果从zuul调用,一HttpClient模拟json请求。设置ContentType :son/utf-8
5、在zuul中单独配置映射