网站首页 > 技术文章 正文
Spring Cloud学习教程1【面试+工作】
1. 统一开发环境
JDK:1.8
Eclipse:4.4.1 luna
Maven:3.2.3
安装文件在课前资料中。
2. 微服务架构
目前微服务是非常火的架构或者说概念,也是在构建大型互联网项目时采用的架构方式。
2.1. 单体架构
单体架构,是指将开发好的项目打成war包,然后发布到tomcat等容器中的应用。
假设你正准备开发一款与Uber和Hailo竞争的出租车调度软件,经过初步会议和需求分析,你可能会手动或者使用基于Spring Boot、Play或者Maven的生成器开始这个新项目,它的六边形架构是模块化的 ,架构图如下:
应用核心是业务逻辑,由定义服务、域对象和事件的模块完成。围绕着核心的是与外界打交道的适配器。适配器包括数据库访问组件、生产和处理消息的消息组件,以及提供API或者UI访问支持的web模块等。
尽管也是模块化逻辑,但是最终它还是会打包并部署为单体式应用。具体的格式依赖于应用语言和框架。例如,许多Java应用会被打包为WAR格式,部署在Tomcat或者Jetty上,而另外一些Java应用会被打包成自包含的JAR格式,同样,Rails和Node.js会被打包成层级目录。
这种应用开发风格很常见,因为IDE和其它工具都擅长开发一个简单应用,这类应用也很易于调试,只需要简单运行此应用,用Selenium链接UI就可以完成端到端测试。单体式应用也易于部署,只需要把打包应用拷贝到服务器端,通过在负载均衡器后端运行多个拷贝就可以轻松实现应用扩展。在早期这类应用运行的很好。
2.2. 单体架构存在的问题
如何解决以上问题呢? --使用微服务架构。
2.3. 什么是微服务?
作者:Martin Fowler
2.4. 微服务架构的特征
2.5. 微服务架构示例
每一个应用功能区都使用微服务完成。
3. SpringCloud简介
3.1. 简介
SpringCloud项目的官方网址:
http://projects.spring.io/spring-cloud/
3.2. SpringCloud子项目
Component | Camden.SR7 | Dalston.SR3 | Edgware.M1 | Finchley.M2 | Finchley.BUILD-SNAPSHOT | 备注 |
spring-cloud-aws | 1.1.4.RELEASE | 1.2.1.RELEASE | 1.2.1.RELEASE | 2.0.0.M1 | 2.0.0.BUILD-SNAPSHOT | 用于简化整合AmazonWebService的组件 |
spring-cloud-bus | 1.2.2.RELEASE | 1.3.1.RELEASE | 1.3.1.RELEASE | 2.0.0.M1 | 2.0.0.BUILD-SNAPSHOT | 事件、消息总线,用于传播集群中的状态变化或事件。 |
spring-cloud-cli | 1.2.4.RELEASE | 1.3.4.RELEASE | 1.4.0.M1 | 2.0.0.M1 | 2.0.0.BUILD-SNAPSHOT | 用于在Groovy平台创建SpringCloud应用。 |
spring-cloud-commons | 1.1.9.RELEASE | 1.2.3.RELEASE | 1.3.0.M1 | 2.0.0.M2 | 2.0.0.BUILD-SNAPSHOT | 服务发现、负载均衡、熔断机制这种模式为Spring Cloud客户端提供了一个通用的抽象层。 |
spring-cloud-contract | 1.0.5.RELEASE | 1.1.3.RELEASE | 1.2.0.M1 | 2.0.0.M2 | 2.0.0.BUILD-SNAPSHOT | |
spring-cloud-config | 1.2.3.RELEASE | 1.3.2.RELEASE | 1.4.0.M1 | 2.0.0.M2 | 2.0.0.BUILD-SNAPSHOT | 配置管理工具,支持使用git、svn等存储配置文件。并在支持客户端配置信息的刷新,加密解密配置内容等。 |
spring-cloud-netflix | 1.2.7.RELEASE | 1.3.4.RELEASE | 1.4.0.M1 | 2.0.0.M2 | 2.0.0.BUILD-SNAPSHOT | 核心组件,对多个NetflixOSS开源套件进行整合。 |
spring-cloud-security | 1.1.4.RELEASE | 1.2.1.RELEASE | 1.2.1.RELEASE | 2.0.0.M1 | 2.0.0.BUILD-SNAPSHOT | 安全工具包。 |
spring-cloud-cloudfoundry | 1.0.1.RELEASE | 1.1.0.RELEASE | 1.1.0.RELEASE | 2.0.0.M1 | 2.0.0.BUILD-SNAPSHOT | 整合Pivotal Cloudfoundry(Vmware推出的业界第一个开源PaaS云平台)支持。 |
spring-cloud-consul | 1.1.4.RELEASE | 1.2.1.RELEASE | 1.2.1.RELEASE | 2.0.0.M1 | 2.0.0.BUILD-SNAPSHOT | 服务发现与配置管理工具 |
spring-cloud-sleuth | 1.1.3.RELEASE | 1.2.4.RELEASE | 1.3.0.M1 | 2.0.0.M2 | 2.0.0.BUILD-SNAPSHOT | Spring Cloud应用的分布式跟踪实现。 |
spring-cloud-stream | Brooklyn.SR3 | Chelsea.SR2 | Ditmars.M2 | Elmhurst.M1 | Elmhurst.BUILD-SNAPSHOT | 通过Redis、RabbitMQ、Kafka实现的消息微服务。 |
spring-cloud-zookeeper | 1.0.4.RELEASE | 1.1.2.RELEASE | 1.2.0.M1 | 2.0.0.M1 | 2.0.0.BUILD-SNAPSHOT | 基于ZooKeeper的服务发现与配置管理组件。 |
spring-boot | 1.4.5.RELEASE | 1.5.4.RELEASE | 1.5.6.RELEASE | 2.0.0.M3 | 2.0.0.M3 | |
spring-cloud-task | 1.0.3.RELEASE | 1.1.2.RELEASE | 1.2.0.RELEASE | 2.0.0.M1 | 2.0.0.RELEASE | 用于快速构建数据处理的应用。 |
spring-cloud-vault | 1.0.2.RELEASE | 1.1.0.M1 | 2.0.0.M2 | 2.0.0.BUILD-SNAPSHOT | ||
spring-cloud-gateway | 1.0.0.M1 | 2.0.0.M2 | 2.0.0.BUILD-SNAPSHOT | SpringCloud网关相关的整合实现。 |
3.3. 版本说明
官方版本:
可见,目前DalstonSR3版本是最新的稳定版,所以我们学习的过程中,就是使用的这个版本。
3.4. SpringCloud框架特点
4. 使用SpringBoot实现微服务
在正式学习Spring Cloud之前我们先使用SpringBoot实现一个微服务。
业务非常简单:
1、 商品微服务:通过商品id查询商品的服务;
2、 订单微服务:创建订单时通时,通过调用商品的微服务进行查询商品数据;
图示:
说明:
1、 对于商品微服务而言,商品微服务是服务的提供者,订单微服务是服务的消费者;
2、 对于订单微服务而言,订单微服务是服务的提供者,人是服务的消费者。
4.1. 实现商品微服务
4.1.1. 创建工程
4.1.2. 导入依赖
重点是导入SpringBoot的依赖:
4.1.3. 创建实体Item
4.1.4. 编写ItemService
编写ItemService用于实现具体的商品查询逻辑,为了演示方便,我们并不真正的连接数据库,而是做模拟实现。
4.1.5. 编写ItemController
@RestController注解的说明:
从源码可以看出,这是一个组合注解,组合了@Controller和@Response注解。相当于我们同时写了这2个注解。
@GetMapping注解的说明:
@GetMapping注解是 @RequestMapping(method = RequestMethod.GET) 简写方式。其功能都是一样的。
同理还有其它注解:
4.1.6. 编写程序入口
4.1.7. 创建application.yml配置文件
SpringBoot以及SpringCloud项目支持yml和properties格式的配置文件。
yml格式是YAML(Yet Another Markup Language)编写的格式,YAML和properties格式的文件是可以相互转化的。如:
等价于:
配置文件的示例:
server:
port: 8081 #服务端口
4.1.8. 启动程序测试
可以看到已经通过微服务查询到数据。
4.2. 实现订单微服务
4.2.1. 创建工程
4.2.2. 导入依赖
4.2.3. 创建订单Order实体
4.2.4. 创建订单详情OrderDetail实体
订单与订单详情是一对多的关系。
4.2.5. 将商品微服务中的Item类拷贝到当前工程
4.2.6. 编写OrderService
该Service实现的根据订单Id查询订单的服务,为了方便测试,我们将构造数据实现,不采用查询数据库的方式。
4.2.7. 实现ItemService
4.2.8. 编写OrderController
4.2.9. 编写程序入口
4.2.10. 编写application.yml配置文件
server:
port: 8082 #服务端口
4.2.11. 启动测试
测试结果可见,查询订单时,同时也将商品数据查询到。
4.3. 添加okHttp的支持
okhttp是一个封装URL,比HttpClient更友好易用的工具。目前似乎okhttp更流行一些。
官网:http://square.github.io/okhttp/
RestTemplate底层默认使用的jdk的标准实现,如果我们想让RestTemplate的底层使用okhttp,非常简单:
1、 添加okhttp依赖
2、 设置requestFactory
return new RestTemplate(new OkHttp3ClientHttpRequestFactory());
测试:
结果:
测试结果是一样的。
4.4. 解决订单系统中的url硬编码问题
通过以上的测试我们发现,在订单系统中要调用商品微服务中的查询接口来获取数据,在订单微服务中将url硬编码到代码中,这样显然不好,因为,运行环境一旦发生变化这个url地址将不可用。
如何解决呢?
解决方案:将url地址写入到application.yml配置文件中。
实现:
修改application.yml文件:
server:
port: 8082 #服务端口
itcast:
item:
url: http://127.0.0.1:8081/item/
修改ItemService中的实现:
测试:
4.5. 继续优化解决硬编码的问题
在SpringBoot中使用@ConfigurationProperties注解可以非常简单的将配置文件中的值映射成对象。
第一步,创建ItemProperties类:
第二步,创建OrderProperties类:
第三步,在Itemservice中注入该对象:
可以看出,这种解决方案比第一种好很多。更加的方便的。
思考:这样是否还存在问题?如果商品的微服务有多个怎么办?
5. SpringCloud快速入门
5.1. 分析硬编码的问题
通过前面5.4、5.5的实现,我们视乎已经解决了url硬编码的问题,但是我们想想:
1、 如果商品微服务的ip地址发生了变更,订单微服务中的配置文件也需要跟着修改
2、 如果商品微服务有多个,那么在订单微服务中又该如何写地址?
那应该怎么解决呢? --通过服务注册、发现的机制来完成。
5.2. 微服务注册与发现
原理示意图:
由上图可以看出:
1、 服务提供者将服务注册到注册中心
2、 服务消费者通过注册中心查找服务
3、 查找到服务后进行调用(这里就是无需硬编码url的解决方案)
4、 服务的消费者与服务注册中心保持心跳连接,一旦服务提供者的地址发生变更时,注册中心会通知服务消费者
5.3. 注册中心Eureka
SpringCloud提供了多种注册中心的支持,如:Eureka、ZooKeeper等。推荐使用Eureka。
5.3.1. 原理
Eureka包含两个组件:Eureka Server和Eureka Client。
Eureka Server提供服务注册服务,各个节点启动后,会在Eureka Server中进行注册,这样EurekaServer中的服务注册表中将会存储所有可用服务节点的信息,服务节点的信息可以在界面中直观的看到。
Eureka Client是一个java客户端,用于简化与Eureka Server的交互,客户端同时也就别一个内置的、使用轮询(round-robin)负载算法的负载均衡器。
在应用启动后,将会向Eureka Server发送心跳,默认周期为30秒,如果Eureka Server在多个心跳周期内没有接收到某个节点的心跳,Eureka Server将会从服务注册表中把这个服务节点移除(默认90秒)。
Eureka Server之间通过复制的方式完成数据的同步,Eureka还提供了客户端缓存机制,即使所有的Eureka Server都挂掉,客户端依然可以利用缓存中的信息消费其他服务的API。综上,Eureka通过心跳检查、客户端缓存等机制,确保了系统的高可用性、灵活性和可伸缩性。
5.3.2. 编写Eureka Server
第一步:创建Maven工程:
第二步,导入依赖:
这里需要导入SpringCloud的管理依赖。
第三步,编写程序启动类:
第四步,编写application.yml配置文件:
server:
port: 6868 #服务端口
eureka:
client:
registerWithEureka:false#是否将自己注册到Eureka服务中,本身就是所有无需注册
fetchRegistry:false#是否从Eureka中获取注册信息
serviceUrl:#Eureka客户端与Eureka服务端进行交互的地址
defaultZone:http://127.0.0.1:${server.port}/eureka/
第五步,启动程序做测试:
5.3.3. 将商品微服务注册到Eureka
接下来,我们需要将商品的微服务注册到Eureka服务中。
第一步:修改pom文件,引入SpringCloud的管理依赖以及eureka服务依赖。
第二步,修改application.yml配置文件:
server:
port: 8081 #服务端口
spring:
application:
name: itcast-microservice-item #指定服务名
eureka:
client:
registerWithEureka:true#是否将自己注册到Eureka服务中,默认为true
fetchRegistry:true#是否从Eureka中获取注册信息,默认为true
serviceUrl:#Eureka客户端与Eureka服务端进行交互的地址
defaultZone: http://127.0.0.1:6868/eureka/
instance:
prefer-ip-address:true#将自己的ip地址注册到Eureka服务中
第三步,修改启动类,增加@EnableDiscoveryClient注解:
第四步,启动测试:
至此,我们已经将自己的微服务注册到Eureka server中了。
5.4. 订单系统从Eureka发现服务
之前我们在订单系统中是将商品微服务的地址进行了硬编码,现在,由于已经将商品服务注册到Eureka中,所以,只需要从Eureka中发现服务即可。
第一步,在订单系统中添加依赖:
第二步,修改application.yml配置文件:
server:
port: 8082 #服务端口
itcast:
item:
url: http://127.0.0.1:8081/item/
spring:
application:
name: itcasst-microservice-order #指定服务名
eureka:
client:
registerWithEureka:false#是否将自己注册到Eureka服务中,默认为true
fetchRegistry:true#是否从Eureka中获取注册信息,默认为true
serviceUrl:#Eureka客户端与Eureka服务端进行交互的地址
defaultZone: http://127.0.0.1:6868/eureka/
第三步,修改ItemService的实现逻辑:
第四步,在启动类中添加@EnableDiscoveryClient注解
第五步,启动测试
可以看到以及获取到数据,但是,我们发现响应的数据变成了xml结构。
5.5. 解决响应变成xml的问题
由于我们引入了eurekaserver的依赖,导致破坏了之前SpringMVC默认的配置,从而导致了响应成了xml。
解决方法:排除eurekaserver中的xml依赖,如下:
测试结果:
6. 注册中心Eureka
6.1. 为Eureka添加用户认证
在前面的示例中,我们可以看到我们需要登录即可访问到Eureka服务,这样其实是不安全的。
接下来,我们为Eureka添加用户认证。
第一步,为itcast-microservice-eureka添加安全认证依赖:
第二步,增加application.yml配置文件:
security:
basic:
enable:true#开启基于HTTP basic的认证
user:#配置用户的账号信息
name: itcast
password: itcast123
第三步,重新启动Eureka服务进行测试:
输入正确的用户名密码即可登录。
这时,服务提供者注册到Eureka时会报错:
所以,需要在服务注册时也需要设置用户名和密码。
6.1.1. 服务注册时设置账户信息
服务注册到有认证需求的注册中心时,需要设置如下信息:
http://USER:PASSWORD@127.0.0.1:6868/eureka/
配置:
重新启动测试:
可以看到已经注册到了Eureka服务注册中心。
6.2. Eureka的自我保护模式
如图,当前Eureka进入了自我保护模式。
所以,一般进入自我保护模式,无需处理。如果,需要禁用自我保护模式,只需要在配置文件中添加配置即可:
eureka:
client:
registerWithEureka:false#是否将自己注册到Eureka服务中,本身就是所有无需注册
fetchRegistry:false#是否从Eureka中获取注册信息
serviceUrl:#Eureka客户端与Eureka服务端进行交互的地址
defaultZone: http://127.0.0.1:${server.port}/eureka/
server:
enable-self-preservation:false#禁用自我保护模式
重新启动服务查看效果:
提示,如果禁用自我保护模式,在网络通信故障下会出现问题。
6.3. Eureka的高可用
前面的测试,我们会发现,Eureka服务是一个单点服务,在生产环境就会出现单点故障,为了确保Eureka服务的高可用,我需要搭建Eureka服务的集群。
搭建Eureka集群非常简单,只要启动多个Eureka服务并且让这些服务之间彼此进行注册即可实现。
第一步,修改itcast-microservice-eureka的application.yml文件:
server:
port:6868#服务端口
spring:
application:
name: itcasst-microservice-eureka #指定服务名
eureka:
client:
registerWithEureka:true#是否将自己注册到Eureka服务中,本身就是所有无需注册
fetchRegistry:true#是否从Eureka中获取注册信息
serviceUrl:#Eureka客户端与Eureka服务端进行交互的地址
defaultZone: http://itcast:itcast123@127.0.0.1:6869/eureka/
server:
enable-self-preservation:true#禁用自我保护模式
security:
basic:
enable:true#开启基于HTTP basic的认证
user:#配置用户的账号信息
name: itcast
password: itcast123
第二步,修改配置文件,再启动一个Eureka服务,进行重启测试:
server:
port:6869#服务端口
spring:
application:
name: itcast-microservice-eureka #指定服务名
eureka:
client:
registerWithEureka:true#是否将自己注册到Eureka服务中,本身就是所有无需注册
fetchRegistry:true#是否从Eureka中获取注册信息
serviceUrl:#Eureka客户端与Eureka服务端进行交互的地址
defaultZone: http://itcast:itcast123@127.0.0.1:6868/eureka/
server:
enable-self-preservation:true#禁用自我保护模式
security:
basic:
enable:true#开启基于HTTP basic的认证
user:#配置用户的账号信息
name: itcast
password: itcast123
测试结果:
可以看到,2个Eureka服务进行了彼此注册。
6.4. 将服务注册到Eureka集群
服务注册到Eureka集群时,可以指定多个,也可以指定一个Eureka服务(因为Eureka服务集群间彼此互联)。
修改itcast-microservice-item的application.yml配置文件:
server:
port: 8081 #服务端口
spring:
application:
name: itcast-microservice-item #指定服务名
logging:
level:
org.springframework: DEBUG
eureka:
client:
registerWithEureka:true#是否将自己注册到Eureka服务中,默认为true
fetchRegistry:true#是否从Eureka中获取注册信息,默认为true
serviceUrl:#Eureka客户端与Eureka服务端进行交互的地址
defaultZone:http://itcast:itcast123@127.0.0.1:6868/eureka/,http://itcast:itcast123@127.0.0.1:6869/eureka/
instance:
prefer-ip-address:true#将自己的ip地址注册到Eureka服务中
重启启动,测试:
可以通过停止Eureka服务进行测试,结果会发现集群是高可用。
6.5. 指定服务的IP地址
在服务的提供者配置文件中可以指定ip地址,如下:
eureka:
client:
registerWithEureka:true#是否将自己注册到Eureka服务中,默认为true
fetchRegistry:true#是否从Eureka中获取注册信息,默认为true
serviceUrl:#Eureka客户端与Eureka服务端进行交互的地址
defaultZone: http://itcast:itcast123@127.0.0.1:6868/eureka/
instance:
prefer-ip-address:true#将自己的ip地址注册到Eureka服务中
ip-address: 127.0.0.1
6.6. 指定实例id
通过instance-id 参数指定服务注册到Eureka中的服务实例id:
eureka:
client:
registerWithEureka:true#是否将自己注册到Eureka服务中,默认为true
fetchRegistry:true#是否从Eureka中获取注册信息,默认为true
serviceUrl:#Eureka客户端与Eureka服务端进行交互的地址
defaultZone: http://itcast:itcast123@127.0.0.1:6868/eureka/
instance:
prefer-ip-address:true#将自己的ip地址注册到Eureka服务中
ip-address: 127.0.0.1
instance-id: ${spring.application.name}:${server.port} #指定实例id
7. 使用Ribbon实现负载均衡
首先,我们思考一个问题,如果为同一个的提供者在Eureka中注册了多个服务,那么客户端该如何选择服务呢?
这时,就需要在客户端实现服务的负载均衡。
在Spring Cloud中推荐使用Ribbon来实现负载均衡。
7.1. Ribbon简介
7.2. 架构
7.3. 开始使用Ribbon
7.3.1. 为itcast-microservice-order增加ribbon依赖
其实,该依赖是可以省略的,因为>spring-cloud-starter-eureka-server中已经包含了spring-cloud-starter-ribbon:
7.3.2. 为RestTemplate设置@LoadBalanced注解
这样,RestTemplate就具备了负载均衡的功能。
7.3.3. 改造ItemService的实现
之前的实现:
改造后的实现:
可以发现,实现更加简化了。
7.3.4. 重启订单服务进行测试
测试结果:
结果显示,可以正常获取到商品数据。
内部原理:
在执行请求前会经过org.springframework.cloud.client.loadbalancer.LoadBalancerInterceptor这个拦截器,并且通过org.springframework.cloud.netflix.ribbon.RibbonLoadBalancerClient中,通过serverId查找服务地址,然后在去做真正的请求。
7.3.5. 测试负载均衡
测试方法:
第一步,启动2个itcast-microservice-item服务(多个也可以)
第二步,编写测试单元测试用例,导入测试依赖:
第三步,编写测试用例:
测试结果:
7.4. 设置负载均衡的为随机
配置:
itcast-microservice-item:
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
测试:
7.5. 其它策略
接口:com.netflix.loadbalancer.IRule,其实现类:
默认策略:
其它策略:
建议:采用默认策略即可。
8. 容错保护:Hystrix
8.1. 分析
8.2. 雪崩效应
在微服务架构中通常会有多个服务层调用,基础服务的故障可能会导致级联故障,进而造成整个系统不可用的情况,这种现象被称为服务雪崩效应。服务雪崩效应是一种因“服务提供者”的不可用导致“服务消费者”的不可用,并将不可用逐渐放大的过程。
如果下图所示:A作为服务提供者,B为A的服务消费者,C和D是B的服务消费者。A不可用引起了B的不可用,并将不可用像滚雪球一样放大到C和D时,雪崩效应就形成了。
8.3. Hystrix简介
主页:https://github.com/Netflix/Hystrix/
8.4. 原理说明
正常情况:
当对特定服务的呼叫达到一定阈值时(Hystrix中的默认值为5秒内的20次故障),电路打开,不进行通讯。并且是一个隔离的线程中进行的。
8.5. 快速入门
在itcast-microservice-order系统中增加Hystrix实现容错。
8.5.1. 导入依赖
8.5.2. 修改ItemService的queryItemById方法
8.5.3. 在启动类OrderApplication添加@EnableHystrix注解
8.5.4. 重新启动进行测试
测试一切正常。
接下来,我们把商品服务停止进行测试:
可以看到,订单服务正常,但是查询商品服务已停止服务,查询到的是错误信息。
由此可见,商品服务的宕机并没有影响订单服务的正常工作,起到的容错效果。
猜你喜欢
- 2024-09-08 精讲RestTemplate第7篇-自定义请求失败异常处理
- 2024-09-08 java实现调用http请求的几种常见方式
- 2024-09-08 深度原理学习——白话TCP与HTTP的keep–alive机制
- 2024-09-08 有了WebClient还在用RestTemplate?
- 2024-09-08 Spring Boot外部接口调用:使用RestTemplate与WebClient操控HTTP
- 2024-09-08 Java服务优雅上下线(java项目如何上线)
- 2024-09-08 Spring 框架里的 HTTP 调用,RestTemplate 还是 WebClient
- 2024-09-08 微服务中如何使用RestTemplate优雅调用API(详细分析)
- 2024-09-08 真不是吹,Spring 里这款牛逼的网络工具库你可能没用过
- 2024-09-08 Java工具类封装微服务间HTTP通信(java md5工具类)
- 02-21走进git时代, 你该怎么玩?_gits
- 02-21GitHub是什么?它可不仅仅是云中的Git版本控制器
- 02-21Git常用操作总结_git基本用法
- 02-21为什么互联网巨头使用Git而放弃SVN?(含核心命令与原理)
- 02-21Git 高级用法,喜欢就拿去用_git基本用法
- 02-21Git常用命令和Git团队使用规范指南
- 02-21总结几个常用的Git命令的使用方法
- 02-21Git工作原理和常用指令_git原理详解
- 最近发表
- 标签列表
-
- cmd/c (57)
- c++中::是什么意思 (57)
- sqlset (59)
- ps可以打开pdf格式吗 (58)
- phprequire_once (61)
- localstorage.removeitem (74)
- routermode (59)
- vector线程安全吗 (70)
- & (66)
- java (73)
- org.redisson (64)
- log.warn (60)
- cannotinstantiatethetype (62)
- js数组插入 (83)
- resttemplateokhttp (59)
- gormwherein (64)
- linux删除一个文件夹 (65)
- mac安装java (72)
- reader.onload (61)
- outofmemoryerror是什么意思 (64)
- flask文件上传 (63)
- eacces (67)
- 查看mysql是否启动 (70)
- java是值传递还是引用传递 (58)
- 无效的列索引 (74)