热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

四.SpringCloud负载均衡与调用

1.Ribbon概述1.1Ribbon是什么SpringCloudRibbon是基于NetflixRibbon实现的一套客户端,是负载均衡的工具。Ribbon是Netflix发布的

1. Ribbon概述


1.1 Ribbon是什么

SpringCloud Ribbon是基于Netflix Ribbon实现的一套客户端,是负载均衡的工具。

Ribbon是Netflix发布的开源项目,主要功能是提供客户端的软件复杂均衡算法和服务调用。Ribbon客户端组件提供一系列完整的配置项如连接超时、重试等。简单的说,就是在配置文件中列出Load Balancer(负载均衡简称LB)后面所有的机器,Ribbon会自动的帮助你基于某种规则(如简单轮询,随机连接等)去连接这些机器。也可以使用Ribbon实现自定义的负载均衡算法。


1.2 Ribbon能做什么

主要是负载均衡(LB):所谓负载均衡,简单的说就是将用户的请求平摊的分配到多个服务上,从而达到系统的HA(High Available高可用),常见的负载均衡有软件Nginx、LVS,硬件F5等。

Ribbon本地负载均衡客户端和Nginx服务端负载均衡的区别:



  • Nginx是服务器负载均衡,客户端所有请求都会交给Nginx,然后由Nginx实现转发请求,即负载均衡是由服务端实现的。

  • Ribbon是本地负载均衡,在调用微服务接口时候,会在注册中心上获取注册信息服务列表之后缓存到JVM本地,从而在本地实现RPC远程服务调用技术。

负载均衡又分为两类,分别可以对应于Nginx和Ribbon:



  • 集中式LB:即在服务的消费方和提供方之间使用独立的LB设施(可以是硬件,如F5,也可以是软件,如Nginx),由该设施负责把访问请求通过某种策略转发至服务的提供方。

  • 进程内LB:将LB逻辑集成到消费方,消费方从服务注册中心获知有哪些地址可用,然后自己再从这些地址中选择出一个合适的服务器,Ribbon就属于进程内LB,它只是一个类库,集成于消费方进程,消费方通过它来获取到服务提供方的地址。

Ribbon实际上就是负载均衡 + RestTemplate调用


2. Ribbon使用案例


2.1 架构说明

Ribbon其实就是一个负载均衡的客户端组件,他可以和其他所需请求的客户端结合使用,和eureka结合只是其中的一个实例。Ribbon在工作的时候分两步:



  • 先选择EurekaServer,优先选择同一个区域内负载较少的Server



  • 根据用户指定的策略,从Server取到的服务注册列表中选择一个地址



其中Ribbon提供了多种的负载均衡策略,如轮询、随机和根据响应时间加强等。


技术分享图片
2.2 pom.xml

在POM文件中我们引入了如下依赖:



org.springframework.cloud
spring-cloud-starter-netflix-eureka-client

点开该依赖的源码,我们发现事实上该依赖内部已经引入了Ribbon,其引入Ribbon的源码如下:


org.springframework.cloud
spring-cloud-starter-netflix-ribbon
2.2.1.RELEASE
compile

我们在Maven的依赖中也可以看到,在引入 spring-cloud-starter-netflix-eureka-client 的同时我们就已经引入了 **spring-cloud-starter-netflix-ribbon **,所以我们没必要单独添加Ribbon的依赖。


技术分享图片
1.3 RestTemplate使用

RestTemplate官方说明可以在RestTemplate官方API查看,下面简要说明其主要方法



  • getForObject方法:返回对象为响应体数据转化成的对象,基本上可以理解为Json对象。

  • getForEntity方法:返回对象为ResponseEntity对象,包含了响应中的一些重要信息,比如响应头、响应状态码、响应体等。

@RestController
@Slf4j
@RequestMapping("/consumer")
public class OrderController {
@Resource
private RestTemplate restTemplate;
// private static final String PAYMENT_URL = "http://localhost:8001";
private static final String PAYMENT_SRV = "http://CLOUD-PAYMENT-SERVICE";
@GetMapping("/payment/get/{id}")
public CommonResult getPayment(@PathVariable("id") Long id) {
return restTemplate.getForObject(PAYMENT_SRV
+ "/payment/get/"
+ id, CommonResult.class);
}
@GetMapping("/payment/getForEntity/{id}")
public CommonResult getPayment2(@PathVariable("id") Long id) {
ResponseEntity entity =
restTemplate.getForEntity(PAYMENT_SRV
+ "/payment/get/" + id, CommonResult.class);
if (entity.getStatusCode().is2xxSuccessful()) {
log.info("===> " + entity.getStatusCode()
+ "\t" + entity.getHeaders());
return entity.getBody(); //返回请求体
} else {
return new CommonResult<>(444, "操作失败");
}
}
}

在后台控制台也输出了状态码和请求头的如下日志:

===> 200 OK [Content-Type:"application/json", Transfer-Encoding:"chunked", Date:"Thu, 28 Jan 2021 15:44:50 GMT", Keep-Alive:"timeout=60", Connection:"keep-alive"]

3. Ribbon核心组件IRule接口


3.1 IRule理解

它可以根据特定算法从服务列表中选取一个要访问的服务

IRule是一个接口,其源码如下:

package com.netflix.loadbalancer;
/**
* Interface that defines a "Rule" for a LoadBalancer. A Rule can be thought of
* as a Strategy for loadbalacing. Well known loadbalancing strategies include
* Round Robin, Response Time based etc.
*
* @author stonse
*
*/
public interface IRule{
/*
* choose one alive server from lb.allServers or
* lb.upServers according to key
*
* @return choosen Server object. NULL is returned if none
* server is available
*/
public Server choose(Object key);

public void setLoadBalancer(ILoadBalancer lb);

public ILoadBalancer getLoadBalancer();
}

以下是IRule接口的部分实现,这些实现分别对应了若干负载均衡算法


技术分享图片

以下简要说明7种主要的负载均衡算法,这些负载均衡算法均是抽象类com.netflix.loadbalancer.AbstractLoadBalancerRule 的实现,而给抽象类实现了IRule接口:



  • com.netflix.loadbalancer.RoundRobinRule:轮询,为默认的负载均衡算法

  • com.netflix.loadbalancer.RandomRule:随机

  • com.netflix.loadbalancer.RetryRule:先按照RoundRobinRule(轮询)的策略获取服务,如果获取服务失败则在指定时间内进行重试,获取可用的服务

  • com.netflix.loadbalancer.WeightedResponseTimeRule:对RoundRobinRule的扩展,响应速度越快的实例选择权重越大,越容易被选择。

  • com.netflix.loadbalancer.BestAvailableRule:先过滤掉由于多次访问故障而处于断路器跳闸状态的服务,然后选择一个并发量最小的服务

  • com.netflix.loadbalancer.AvailabilityFilteringRule:先过滤掉故障实例,再选择并发较小的实例

  • com.netflix.loadbalancer.ZoneAvoidanceRule:复合判断Server所在区域的性能和Server的可用性选择服务器


3.2 如何替换负载均衡算法

服务消费者80添加轮询算法配置类

首先我们应该明确是服务消费方采用轮询算法来访问同一服务提供方的不同微服务实例,所以我们应该在服务消费方80方的微服务中添加轮询算法配置类。

在添加配置类时,有必须要注意的点,就是官方文档明确给出了警告:这个自定义的轮询算法配置类不能放在@ComponentScan注解所扫描的当前包下以及子包下,否则自定义的这个配置类就会被所有Ribbon客户端所共享,就达不到特殊化定制的目的了。换句话说,如果这个配置类我们能够被@ComponentScan注解扫描到,那么访问所有的微服务提供方的具体实例时,我们都会采取配置类中的算法,如果要特殊化定制 - 即指定访问某些微服务提供方时采用配置的轮询算法,那么我们就应该使这个配置类让@ComponentScan注解扫描不到,我们知道在主启动类的@SpringBootApplication注解中,其实这个注解包含了@SpringBootConfiguration@EnableAutoConfiguration@ComponentScan这三个注解,所以我们写的轮询算法配置类不能和主启动类在同一个包下,所以我们需要建新的包,实现定制轮询算法的配置类:

package com.polaris.myrule;
/**
* @author polaris
*/
@Configuration
public class MySelfRule {
@Bean
public IRule myRule() {
return new RandomRule(); //定义随机负载均衡算法
}
}

包结构的内容如下,我们可以看到,轮询算法配置类在主启动类的@ComponentScan扫描不到的包下:


技术分享图片

服务消费者80主启动类中添加@RibbonClient注解

@SpringBootApplication
@EnableEurekaClient
//访问的微服务为CLOUD-PAYMENT-SERVICE,采用配置文件中的轮询算法
@RibbonClient(name = "CLOUD-PAYMENT-SERVICE", cOnfiguration= MySelfRule.class)
public class OrderMain {
public static void main(String[] args) {
SpringApplication.run(OrderMain80.class);
}
}

测试

测试发现我们用服务消费方访问服务提供方的微服务时,8001和8002不再交替轮询访问,而是随机访问。


4. Ribbon负载均衡算法


4.1 默认负载均衡算法(轮询)原理

轮询负载均衡算法原理:Rest接口第几次请求数 % 服务器集群总数量 = 实际调用服务器位置下标, 每次服务重启后Rest接口计数从1开始。

List instances = discoveryClient.getInstances("CLOUD-PAYMENT-SERVICE")

根据服务方的服务名,获取其所有实例,如有以下实例:






















List[0]List[1]
服务名payment8001payment8002
服务地址127.0.0.1:8001127.0.0.1:8002

这两个实例组合成一个集群,共2台机器,集群总数为2,按照轮询负载均衡算法原理:



  • 请求总数为1时,1 % 2 = 1,对应下标位置是1,获得服务地址127.0.0.1:8001



  • 请求总数为2时,2 % 2 = 0,对应下标位置是0,获得服务地址127.0.0.1:8002



  • 请求总数为3时,3 % 2 = 1,对应下标位置是1,获得服务地址127.0.0.1:8001



  • ...




4.2 轮询源码分析

com.netflix.loadbalancer.RoundRobinRule源码的负载均衡算法部分分析如下(代码中标注了中文注释):

package com.netflix.loadbalancer;
/**
* The most well known and basic load balancing strategy, i.e. Round Robin Rule.
*/
public class RoundRobinRule extends AbstractLoadBalancerRule {
//...
public Server choose(ILoadBalancer lb, Object key) {
if (lb == null) {
log.warn("no load balancer");
return null;
}
Server server = null;
int count = 0;
while (server == null && count++ <10) {
//获得还活着的健康的服务实例(机器)即可达的,也就是Status为up的实例
List reachableServers = lb.getReachableServers();
//获取所有服务实例,无论是死是活,只要注册进服务中心即可
List allServers = lb.getAllServers();
//Status为up的服务实例数量
int upCount = reachableServers.size();
//所有服务实例的数量,对应上述原理分析中的服务器集群总数量
int serverCount = allServers.size();
//如果没有可达的服务实例的话,直接报警告
if ((upCount == 0) || (serverCount == 0)) {
log.warn("No up servers available from load balancer: " + lb);
return null;
}
//调用服务器位置下标 = incrementAndGetModulo(服务器集群总数)
int nextServerIndex = incrementAndGetModulo(serverCount);
server = allServers.get(nextServerIndex);//根据下标获取服务实例
if (server == null) {
/* Transient. */
Thread.yield();
continue;
}
if (server.isAlive() && (server.isReadyToServe())) {
return (server);
}
// Next.
server = null;
}
if (count >= 10) {
log.warn("No available alive servers after 10 tries from load balancer: "
+ lb);
}
return server;
}
/**
* Inspired by the implementation of {@link AtomicInteger#incrementAndGet()}.
*
* @param modulo The modulo to bound the value of the counter.
* @return The next value.
*/
private int incrementAndGetModulo(int modulo) {
for (;;) {
int current = nextServerCyclicCounter.get();
int next = (current + 1) % modulo;
if (nextServerCyclicCounter.compareAndSet(current, next))
return next;
}
}
}

4.3 自己实现轮询负载均衡算法

首先我们将服务注册中心(7001/7002构成集群)启动,然后在服务提供方8001/8002中的Controller中添加功能,用来一会儿测试服务消费方80来轮询访问CLOUD-PAYMENT-SERVICE服务:

@GetMapping("/payment/lb")
public String getPaymentLB(){
return serverPort;
}

服务提供方的这个方法就是简单的在页面输出自己的端口号,也就是我们可以在页面区分访问的CLOUD-PAYMENT-SERVICE服务到底对应的是8001实例还是8002实例。

启动8001/8002,将两个服务实例注册进服务注册中心后,我们再改造服务消费方80服务,分为以下四步:



  • 首先我们先让RestTemplate失去Ribbon中的负载均衡能力,取消掉@LoadBalanced注解即可:

@Configuration
public class ApplicationContextConfig {
@Bean
// @LoadBalanced//使用该注解赋予RestTemplate负载均衡的能力
public RestTemplate getRestTemplate() {
return new RestTemplate();
}
}
//applicationContext.xml


  • 然后编写自己的负载均衡接口:


给接口定义了方法instances用于在服务提供方服务的所有服务实例中选择一个具体实例。


public interface LoadBalancer {
/**
* 从服务列表中用负载均衡算法选择出具体的实例
* @param serviceInstances 服务列表
* @return
*/
ServiceInstance instances(List serviceInstances);
}


  • 用轮询负载均衡算法实现负载均衡接口:


RoundRobinRule源码中用for(;;)实现的自旋锁,这里我们用do{} while();实现自旋锁。


@Component
public class MyLB implements LoadBalancer {
private AtomicInteger atomicInteger = new AtomicInteger(0);
public final int getAndIncrement() {
int current;
int next;
//自旋锁
do {
current = this.atomicInteger.get(); //初始值为0
next = current >= 2147483647 ? 0 : current + 1; //最大整数
} while (!this.atomicInteger.compareAndSet(current, next));
System.out.println("===> 访问次数next:" + next);
return next;
}
/**
* 从服务列表中用轮询负载均衡算法选择出具体的实例
* Rest接口第几次请求数 % 服务器集群总数量 = 实际调用服务器位置下标
*
* @param serviceInstances 服务列表
* @return
*/
@Override
public ServiceInstance instances(List serviceInstances) {
int index = getAndIncrement() % serviceInstances.size();
return serviceInstances.get(index);
}
}


  • 最后我们在80服务的Controller中添加方法:

@RestController
@Slf4j
@RequestMapping("/consumer")
public class OrderController {
@Resource
private RestTemplate restTemplate;
@Resource
private LoadBalancer loadBalancer;
@Resource
private DiscoveryClient discoveryClient;
@GetMapping("payment/lb")
public String getPaymentLB() {
//获取服务提供方所有的服务实例
List instances = discoveryClient.getInstances("CLOUD-PAYMENT-SERVICE");
if (instances == null || instances.size() <= 0) {
return null;
}
//采用自己实现的轮询负载均衡算法选择具体实例
ServiceInstance serviceInstance = loadBalancer.instances(instances);
URI uri = serviceInstance.getUri();
return restTemplate.getForObject(uri + "/payment/lb", String.class);
}
}

在浏览器中输入http://localhost/consumer/payment/lb,也就是80端口的服务消费方采用我们自己编写的轮询负载均衡算法访问CLOUD-PAYMENT-SERVICE服务的具体实例,测试成功,在服务消费方80服务的后端控制台也输出了的日志。


5. OpenFeign概述


5.1 OpenFeign是什么?

Feign是一个声名式WebService客户端,使用Feign能让编写WebService客户端更加简单。它的使用方法是定义一个服务接口然后在上面添加注解。Feign也支持可拔插式的编码器和解码器。SpringCloud对Feign进行了封装,使其支持了Spring MVC标准注解和HttpMessageConverters。Feign可以与Eureka和Ribbon组合使用以支持负载均衡。


5.2 Feign能做什么?

Feign旨在使编写Java Http客户端变得更容易。之前我们使用Ribbon + RestTemplate时,利用RestTemplate对Http请求的封装处理,形成了一套模板化的调用方法。但是在实际开发中由于对服务依赖的调用可能不止一处,往往一个接口会被多处调用,所以通常都会针对每个微服务自行封装一些客户端类来包装这些依赖服务的调用。所以Feign在此基础上做了进一步封装,由它来帮助定义和实现依赖服务接口的定义。在Feign的实现下,只需要创建一个接口并使用注解的方式来配置它(例如以前是DAO接口上面标注Mapper注解,现在是一个微服务接口上面标注一个Feign注解),即可完成对服务提供方的接口绑定,简化了使用SpringCloud Ribbon时,自动封装服务调用客户端的开发量。

Feign集成了Ribbon:利用Ribbon维护服务提供方的服务列表信息,并且通过如轮询的算法实现了客户端的负载均衡。而与Ribbon不同的是,通过Feign只需要定义服务绑定接口且以声明式的方式,优雅而简单的实现了服务调用。


5.3 Feign和OpenFeign的区别

Feign已经停止维护,所以我们只需要关注OpenFeign的使用即可,我们现在学习的就是利用OpenFeign实现我们之前用的Ribbon+RestTemplate实现的功能。






















FeignOpenFeign
特点Feign是SpringCloud组件中的一个轻量级RESTful的HTTP服务客户端,Feign内置了Ribbon,用来做客户端的负载均衡,去调用服务注册中心的服务。Feign的使用方式是:使用Feign的注解定义接口,调用这个接口,就可以调用服务注册中心的服务。OpenFeign是SpringCloud在Feign的基础上支持了SpringMVC的注解,如@RequestMapping 等。OpenFeign的 @FeignClient 可以解析SpringMVC的 @RequestMapping 注解下的接口,并通过动态代理的方式产生实现类,实现类中做负载均衡并调用其他服务。
启动器spring-cloud-starter-feignspring-cloud-starter-openfeign

6. OpenFeign使用案例


6.1 接口+注解,新建Module

新建cloud-consumer-feign-order80作为服务消费方服务。

在微服务调用的接口上添加注解@FeignClient,注意OpenFeign在服务消费方使用


6.2 pom.xml

在POM中我们引入了OpenFeign的依赖以及Eureka客户端的依赖




org.springframework.cloud
spring-cloud-starter-openfeign



org.springframework.cloud
spring-cloud-starter-netflix-eureka-client



com.polaris
cloud-api-common
${project.version}



org.springframework.boot
spring-boot-starter-web


org.springframework.boot
spring-boot-starter-actuator



org.springframework.boot
spring-boot-devtools
runtime
true


org.projectlombok
lombok
true


org.springframework.boot
spring-boot-starter-test
test



6.3 yml配置文件

由于80为服务消费方,只是调用服务提供方的服务,所以可以不讲自己注册到服务注册中心。


server:
port: 80
eureka:
client:
register-with-eureka: false # 客户端就不注册入服务注册中心了
service-url:
defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/

6.4 主启动类

注意主启动类上需要添加 @EnableFeignClients 注解,这个注解声明80服务可以使用Feign来实现服务接口的调用。


@SpringBootApplication
@EnableFeignClients //激活Feign功能
public class OrderFeignMain80 {
public static void main(String[] args) {
SpringApplication.run(OrderFeignMain80.class);
}
}

6.5 业务类

前面就提到过,在Feign的实现下,只需要创建一个接口并使用注解的方式来配置,这是什么意思呢,就是我们在服务消费方中的service中编写接口,并在该接口上使用 @FeignClient 注解,这样的话就能够实现对服务提供方的服务调用,首先我们看服务提供方8001中有如下的一个服务:

@RestController
@Slf4j
@RequestMapping("/payment")
public class PaymentController {
//...
@GetMapping("/get/{id}")
public CommonResult getPaymentById(@PathVariable("id") Long id) {
Payment paymentById = paymentService.getPaymentById(id);
log.info("===> payment: " + paymentById);
if(paymentById != null) {
return new CommonResult(200,
"查询成功,端口号:" + serverPort,paymentById);
}
return new CommonResult(400,"查询失败",null);
}
//...
}

在服务消费方80中编写如下的接口即可对服务提供方的服务进行调用:

@Component
@FeignClient("CLOUD-PAYMENT-SERVICE")
public interface PaymentFeignService {
@GetMapping("/payment/get/{id}")
public CommonResult getPaymentById(@PathVariable("id") Long id);
}

@FeignClient 注解中的value值为要调用的服务名称,也就是8001/8002服务提供方注册到Eureka注册中心的服务名,这个注解就是告诉该接口调用哪个服务,而默认OpenFeign会使用轮训的负载均衡算法来调用具体的服务实例,在这个接口中我们需要使用服务提供方服务的哪个具体方法,将该方法作为接口方法写入接口中即可。

然后编写80服务消费方的Controller:

@RestController
@Slf4j
@RequestMapping("/consumer")
public class OrderFeignController {
@Autowired
private PaymentFeignService paymentFeignService;
@GetMapping("/payment/get/{id}")
public CommonResult getPaymentById(@PathVariable("id") Long id) {
return paymentFeignService.getPaymentById(id);
}
}

6.6 测试

我们先启动Eureka的集群注册中心,然后启动服务提供方8001/8002,再启动服务消费方80,访问http://localhost/consumer/payment/get/1,我们可以发现OpenFeign使用轮询的负载均衡算法实现了服务提供方服务接口的调用。


6.7 总结

简言之就是客户端的服务接口使用用 @FeignClient 注解根据服务名称去调用服务提供方的具体服务。


7. OpenFeign超时控制


7.1 超时设置

咱们这里故意设置超时演示出错情况


服务提供方8001故意写暂停程序

@GetMapping("/feign/timeout")
public String paymentFeignTimeout() {
//暂停几秒线程
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
return serverPort;
}

服务消费方80service添加超时方法paymentFeignTimeout,controller添加超时接口paymentFeignTimeout

@Component
@FeignClient("CLOUD-PAYMENT-SERVICE")
public interface PaymentFeignService {
@GetMapping("/payment/feign/timeout")
String paymentFeignTimeout();
}

@RestController
@Slf4j
@RequestMapping("/consumer")
public class OrderFeignController {
@Resource
private PaymentFeignService paymentFeignService;
@GetMapping("/payment/feign/timeout")
public String paymentFeignTimeout() {
//openfeign底层为ribbon,客户端一般默认等待1秒钟
return paymentFeignService.paymentFeignTimeout();
}
}

测试:http://localhost/consumer/payment/feign/timeout

错误页面(OpenFeign默认等待1秒钟,超过后报错)


技术分享图片
7.2 开启OpenFeign客户端超时控制

服务消费者80的yml配置文件中开启OpenFeign客户端超时控制

server:
port: 80
eureka:
client:
register-with-eureka: false # 客户端就不注册入服务注册中心了
service-url:
defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/
# 设置feign客户端超时时间(OpenFeign默认支持ribbon)
ribbon:
# 指的是建立连接所用的时间,适用于网络状态正常的情况下,两端连接所用的时间
ReadTimeout: 5000
# 指的是建立连接后从服务器读取到可用资源所用的时间
ConnectTimeout: 5000

8. OpenFeign日志打印

理解

Feign提供了日志打印功能,我们可以通过配置来调整日志级别,从而了解 Fegin 中 Http 请求的细节。说白了就是对Feign接口的调用情况进行监控和输出。

日志级别



  • NONE 默认的,不显示任何日志

  • BASIC 仅记录请求方法,URL,响应状态码及执行时间

  • HEADERS 除了BASIC中定义的消息外,还有请求和响应的头信息

  • FULL 除了 HEADERS 中定义的信息外,还有请求和响应的正文及元数据

配置日志Bean

@Configuration
public class FeignConfig {
/**
* feignClient配置日志级别
* @return
*/
@Bean
public Logger.Level feignLoggerLevel() {
// 请求和响应的头信息,请求和响应的正文及元数据
return Logger.Level.FULL;
}
}

yml配置文件中开启日志的Feign客户端

server:
port: 80
eureka:
client:
register-with-eureka: false
fetch-registry: true
service-url:
defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka
ribbon:
ReadTimeout: 5000
ConnectTimeout: 5000
logging:
level:
# feign日志以什么级别监控哪个接口
com.polaris.springcloud.service.PaymentFeignService: debug

后台日志查看


技术分享图片


推荐阅读
  • 基于layUI的图片上传前预览功能的2种实现方式
    本文介绍了基于layUI的图片上传前预览功能的两种实现方式:一种是使用blob+FileReader,另一种是使用layUI自带的参数。通过选择文件后点击文件名,在页面中间弹窗内预览图片。其中,layUI自带的参数实现了图片预览功能。该功能依赖于layUI的上传模块,并使用了blob和FileReader来读取本地文件并获取图像的base64编码。点击文件名时会执行See()函数。摘要长度为169字。 ... [详细]
  • Mac OS 升级到11.2.2 Eclipse打不开了,报错Failed to create the Java Virtual Machine
    本文介绍了在Mac OS升级到11.2.2版本后,使用Eclipse打开时出现报错Failed to create the Java Virtual Machine的问题,并提供了解决方法。 ... [详细]
  • 在说Hibernate映射前,我们先来了解下对象关系映射ORM。ORM的实现思想就是将关系数据库中表的数据映射成对象,以对象的形式展现。这样开发人员就可以把对数据库的操作转化为对 ... [详细]
  • 本文介绍了在SpringBoot中集成thymeleaf前端模版的配置步骤,包括在application.properties配置文件中添加thymeleaf的配置信息,引入thymeleaf的jar包,以及创建PageController并添加index方法。 ... [详细]
  • 本文详细介绍了Linux中进程控制块PCBtask_struct结构体的结构和作用,包括进程状态、进程号、待处理信号、进程地址空间、调度标志、锁深度、基本时间片、调度策略以及内存管理信息等方面的内容。阅读本文可以更加深入地了解Linux进程管理的原理和机制。 ... [详细]
  • 1,关于死锁的理解死锁,我们可以简单的理解为是两个线程同时使用同一资源,两个线程又得不到相应的资源而造成永无相互等待的情况。 2,模拟死锁背景介绍:我们创建一个朋友 ... [详细]
  • 本文介绍了lua语言中闭包的特性及其在模式匹配、日期处理、编译和模块化等方面的应用。lua中的闭包是严格遵循词法定界的第一类值,函数可以作为变量自由传递,也可以作为参数传递给其他函数。这些特性使得lua语言具有极大的灵活性,为程序开发带来了便利。 ... [详细]
  • 本文介绍了使用Java实现大数乘法的分治算法,包括输入数据的处理、普通大数乘法的结果和Karatsuba大数乘法的结果。通过改变long类型可以适应不同范围的大数乘法计算。 ... [详细]
  • 本文讨论了如何优化解决hdu 1003 java题目的动态规划方法,通过分析加法规则和最大和的性质,提出了一种优化的思路。具体方法是,当从1加到n为负时,即sum(1,n)sum(n,s),可以继续加法计算。同时,还考虑了两种特殊情况:都是负数的情况和有0的情况。最后,通过使用Scanner类来获取输入数据。 ... [详细]
  • Windows下配置PHP5.6的方法及注意事项
    本文介绍了在Windows系统下配置PHP5.6的步骤及注意事项,包括下载PHP5.6、解压并配置IIS、添加模块映射、测试等。同时提供了一些常见问题的解决方法,如下载缺失的msvcr110.dll文件等。通过本文的指导,读者可以轻松地在Windows系统下配置PHP5.6,并解决一些常见的配置问题。 ... [详细]
  • 本文介绍了C#中数据集DataSet对象的使用及相关方法详解,包括DataSet对象的概述、与数据关系对象的互联、Rows集合和Columns集合的组成,以及DataSet对象常用的方法之一——Merge方法的使用。通过本文的阅读,读者可以了解到DataSet对象在C#中的重要性和使用方法。 ... [详细]
  • 本文介绍了OC学习笔记中的@property和@synthesize,包括属性的定义和合成的使用方法。通过示例代码详细讲解了@property和@synthesize的作用和用法。 ... [详细]
  • 知识图谱——机器大脑中的知识库
    本文介绍了知识图谱在机器大脑中的应用,以及搜索引擎在知识图谱方面的发展。以谷歌知识图谱为例,说明了知识图谱的智能化特点。通过搜索引擎用户可以获取更加智能化的答案,如搜索关键词"Marie Curie",会得到居里夫人的详细信息以及与之相关的历史人物。知识图谱的出现引起了搜索引擎行业的变革,不仅美国的微软必应,中国的百度、搜狗等搜索引擎公司也纷纷推出了自己的知识图谱。 ... [详细]
  • Voicewo在线语音识别转换jQuery插件的特点和示例
    本文介绍了一款名为Voicewo的在线语音识别转换jQuery插件,该插件具有快速、架构、风格、扩展和兼容等特点,适合在互联网应用中使用。同时还提供了一个快速示例供开发人员参考。 ... [详细]
  • 本文介绍了高校天文共享平台的开发过程中的思考和规划。该平台旨在为高校学生提供天象预报、科普知识、观测活动、图片分享等功能。文章分析了项目的技术栈选择、网站前端布局、业务流程、数据库结构等方面,并总结了项目存在的问题,如前后端未分离、代码混乱等。作者表示希望通过记录和规划,能够理清思路,进一步完善该平台。 ... [详细]
author-avatar
锦瑟刻下两段缠绵
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有