Spring Cloud是一系列框架的有序集合。它利用Spring Boot的开发便利性巧妙地简化了分布式系统基础设施的开发,如服务发现注册、配置中心、消息总线、负载均衡、断路器、数据监控等,都可以用Spring Boot的开发风格做到一键启动和部署。Spring Cloud并没有重复制造轮子,它只是将各家公司开发的比较成熟、经得起实际考验的服务框架组合起来,通过Spring Boot风格进行再封装屏蔽掉了复杂的配置和实现原理,最终给开发者留出了一套简单易懂、易部署和易维护的分布式系统开发工具包。
共分四天进行学习

一、微服务基础知识

1.1 项目架构的演变

项目架构的演变:单体应用架构→垂直应用架构→分布式架构→微服务架构
分布式架构的缺点:

  • 抽取服务的粒度较大
  • 服务提供方与调用接口方耦合度较高

为了解决以上缺点,微服务架构被提出。
微服务的优点

  • 通过服务的原子化拆分,以及微服务的独立打包、部署和升级,小团队的服务周期将缩短,运维成本也将大幅下降
  • 微服务遵循单一原则。微服务之间采用Restful等轻量协议传输

微服务的缺点:

  • 微服务过多,服务治理成本高,不利于系统维护
  • 分布式系统开发的技术成本高(容错、分布式事务等)

1.2核心概念

1.2.1远程调用技术

比较流行的远程调用技术:

  • RPC:RPC协议假定某些传输协议的存在,如TCP或UDP,为通信程序之间携带信息数据。在OSI网络通信模型中,RPC跨越了传输层和应用层。RPC使得开发包括网络分布式多程序在内的应用程序更加容易。 下图为RPC的过程。

  • HTTP:发出请求,得到响应,比较简单。
    HTTP与RPC进行比较

1.2.2CAP原理

1.3 常见的微服务框架

1.3.1 SpringCloud介绍

Spring Cloud是一系列框架的有序集合。它利用Spring Boot的开发便利性巧妙地简化了分布式系统基础设施的开发,如服务发现注册、配置中心、消息总线、负载均衡、断路器、数据监控等,都可以用Spring Boot的开发风格做到一键启动和部署。Spring Cloud并没有重复制造轮子,它只是将各家公司开发的比较成熟、经得起实际考验的服务框架组合起来,通过Spring Boot风格进行再封装屏蔽掉了复杂的配置和实现原理,最终给开发者留出了一套简单易懂、易部署和易维护的分布式系统开发工具包。

1.3.2 通过RestTemplate远程调用服务
  • 1、创建商品服务

使用RestTemplate进行服务之间的远程调用,首先创建商品服务product_service,按照MVC设计模式,使用SpringBoot创建一个简单的能够完成增删改查功能的项目,代码比较简单就不再写了。

  • 2 创建订单服务,调用商品服务
    在工程内创建订单服务,跨模块调用商品服务。此处使用HTTP协议进项远程调用。步骤如下:
  1. 在订单服务的启动器类中创建RestTemplate 对象,用来访问商品服务。代码如下:
    /*** 创建RestTemplate对象,交给IOC容器管理,用来访问product_service* @param args*/@Beanpublic RestTemplate restTemplate(){return new RestTemplate();}
  1. 在订单的controller中调用商品服务。代码如下:
package com.runze.order.controller;import com.runze.order.domain.Product;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;@RestController
@RequestMapping("/order")
public class OrderController {@Autowiredprivate RestTemplate restTemplate;/*** 参数:商品ID*  通过订单系统,调用商品服务根据商品id查询商品信息*  1、需要配置商品对象*  2、需要调用商品服务**/@GetMapping("/buy/{id}")public Product findProductById(@PathVariable Long id){Product product = null;product=restTemplate.getForObject("http://127.0.0.1:9001/product/"+id,Product.class);return product;}
}

如上,商品就做为一个的小型的微服务供其他服务调用,但是这种微服务有很多弊端,如下图:

所以,我们使用SpringCloud来创建微服务项目。

二 SpringCloud框架

SpringCloud中常见的组件介绍

2.1 注册中心

注册中心的作用如下图
常见的注册中心有以下四种

2.1.1 Eureka介绍

Eureka是Netflix开发的服务发现框架,本身是一个基于REST的服务,主要用于定位运行在AWS域中的中间层服务,以达到负载均衡和中间层服务故障转移的目的。
SpringCloud将它集成在其子项目spring-cloud-netflix中,以实现SpringCloud的服务发现功能。其工作流程如下图所示:
使用步骤

  1. 搭建eureka server
    1.1 创建工程
    1.2 导入坐标
    <dependencies><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-netflix-eureka-server</artifactId></dependency></dependencies>

1.3 配置application.yml

server:port: 9000eureka:instance:hostname: localhostclient:register-with-eureka: false #是否将自己注册到注册中心fetch-registry: false #是否从eureka中获取注册信息service-url: #暴露给eureka.Client的请求地址defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/

1.4 配置启动类

package com.runze.eureka;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;@SpringBootApplication
//激活eureka Server
@EnableEurekaServer
public class EurekaServerApplication {public static void main(String[] args) {SpringApplication.run(EurekaServerApplication.class,args);}
}
  1. 将服务提供者注册到eureka Server上
    2.1 引入EurekaClient的坐标
        <dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-netfix-eureka-client</artifactId></dependency>

2.2 修改application.yml添加EurekaClient的信息

eureka:client:service-url: #暴露给eureka.Client的请求地址defaultZone: http://localhost:9000/eureka/

2.3修改启动类,添加服务发现的支持(可选)

  1. 服务消费者通过注册中心获取服务列表并调用
    3.1 引入EurekaClient的坐标
    3.2 修改application.yml添加EurekaClient的信息
    3.3修改启动类,添加服务发现的支持(可选)
    这三步同上,不写了
    4.3 获取注册中心中服务提供者的信息
package com.runze.order.controller;import com.runze.order.domain.Product;
import net.bytebuddy.asm.Advice;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;import java.util.List;@RestController
@RequestMapping("/order")
public class OrderController {@Autowiredprivate RestTemplate restTemplate;@Autowiredprivate DiscoveryClient discoveryClient;/*** 参数:商品ID*  通过订单系统,调用商品服务根据商品id查询商品信息*  1、需要配置商品对象*  2、需要调用商品服务**/@GetMapping("/buy/{id}")public Product findProductById(@PathVariable Long id){//从eureka service中获取服务提供者的信息List<ServiceInstance> instances = discoveryClient.getInstances("service-product");ServiceInstance serviceInstance = instances.get(0);//无需将服务提供方的url写死,随用随改。Product product=restTemplate.getForObject("http://"+serviceInstance.getHost()+":"+serviceInstance.getPort()+"/product/"+id,Product.class);return product;}
}
2.1.2 高可用的引入,Eureka Server之间相互注册##### 2.1.3 Eureka的细节问题
  1. 在Eureka的注册中心中显示服务提供者的ip地址
eureka:client:service-url: #暴露给eureka.Client的请求地址defaultZone: http://localhost:9000/eureka/instance:prefer-ip-address: true #使用ip注册instance:instance-id: ${spring.cloud.client.ip-address}:${server.port} #设置Eureka中显示服务提供者的ip地址
  1. 设置续约到期的时间以及发送心跳的间隔
  instance:#设置Eureka中显示服务提供者的ip地址instance-id: ${spring.cloud.client.ip-address}:${server.port}# 设置心跳的间隔lease-renewal-interval-in-seconds: 5# 设置续约到期的时间lease-expiration-duration-in-seconds: 10
  1. Eureka的自我保护机制
eureka:instance:hostname: localhostclient:register-with-eureka: false #是否将自己注册到注册中心fetch-registry: false #是否从eureka中获取注册信息service-url: #暴露给eureka.Client的请求地址defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/server:enable-self-preservation: false #关闭自我保护机制eviction-interval-timer-in-ms: 4000 #测试阶段设置剔除服务间隔

2与3测试阶段可用,服务上线后不建议使用

2.1.4 Eureka源码详解
  1. Spring Boot的自动装载,使用一个小案例演示
package com.runze.damain;import lombok.Data;@Data
public class User {private String name;private Integer id;
}
package com.runze.damain;import org.springframework.context.annotation.Bean;public class UserConfiguration {@Beanpublic User getUser(){User user=new User();user.setName("李华");user.setId(1);return user;}
}
package com.runze.damain;import org.springframework.context.annotation.ImportSelector;
import org.springframework.core.type.AnnotationMetadata;public class UserImportSelector implements ImportSelector{@Overridepublic String[] selectImports(AnnotationMetadata annotationMetadata) {return new String[]{UserConfiguration.class.getName()};}
}
package com.runze.damain;import org.springframework.context.annotation.Import;import java.lang.annotation.*;@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Import(UserImportSelector.class)
public @interface EnableUserBean {}
package com.runze.test;import com.runze.damain.EnableUserBean;
import com.runze.damain.User;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;@EnableUserBean
public class TestEnableUserBean {public static void main(String[] args) {/***@ClassName: -->EnableUserBean--> UserImportSelector--> UserConfiguration--> User*@Param*@Author 李润泽*@Return*@Time*/AnnotationConfigApplicationContext ac=new AnnotationConfigApplicationContext(TestEnableUserBean.class);User bean = ac.getBean(User.class);System.out.println(bean);}
}

运行流程:测试类–>EnableUserBean–> UserImportSelector–> UserConfiguration–> User
2. Eureka的启动流程

2.2 Ribbon

2.2.1Ribbon概述

Spring Cloud Ribbon是一个基于HTTP和TCP的客户端负载均衡工具,它基于Netflix Ribbon实现。通过Spring Cloud的封装,可以让我们轻松地将面向服务的REST模版请求自动转换成客户端负载均衡的服务调用。Spring Cloud Ribbon虽然只是一个工具类框架,它不像服务注册中心、配置中心、API网关那样需要独立部署,但是它几乎存在于每一个Spring Cloud构建的微服务和基础设施中。因为微服务间的调用,API网关的请求转发等内容,实际上都是通过Ribbon来实现的。所以,对Spring Cloud Ribbon的理解和使用,对于我们使用Spring Cloud来构建微服务非常重要

2.2.2 Ribbon功能

1.服务调用

  • 在RestTemplate上加上注解 @LoadBalanced
  • 使用服务名代替IP地址
 @GetMapping("/buy/{id}")public Product findProductById(@PathVariable Long id){Product product=restTemplate.getForObject("http://service_product/product/1",Product.class);return product;}

2.负载均衡
Ribbon是一个典型的客户端负载均衡器,Ribbon会获取服务的所有地址,根据其内部的负载均衡算法,获取本次请求的有效地址。Ribbon的负载均衡策略如下:

3. ribbon源码分析

2.3 Consul

consul是google开源的一个使用go语言开发的服务发现、配置管理中心服务。内置了服务注册与发现框 架、分布一致性协议实现、健康检查、Key/Value存储、多数据中心方案,不再需要依赖其他工具(比如ZooKeeper等)。服务部署简单,只有一个可运行的二进制的包。每个节点都需要运行agent,他有两种运行模式server和client。每个数据中心官方建议需要3或5个server节点以保证数据安全,同时保证server-leader的选举能够正确的进
Consul与Eureka的区别与联系

启动Consul

2.3.1 Consul入门

导入maven坐标

        <!--springcloud 提供的对基于consul的服务发现--><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-consul-discovery</artifactId></dependency><!--actuator的健康检查--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-actuator</artifactId></dependency>

服务提供方的配置

  cloud:consul:host: 127.0.0.1 #consul服务器的主机地址port: 8500 #consul服务器的ip地址discovery:#是否需要注册register: true#注册的实例ID (唯一标志)instance-id: ${spring.application.name}-1#服务的名称service-name: ${spring.application.name}#服务的请求端口port: ${server.port}#指定开启ip地址注册prefer-ip-address: true#当前服务的请求ipip-address: ${spring.cloud.client.ip-address}

服务消费者的配置

  cloud:consul:host: 127.0.0.1 #consul服务器的主机地址port: 8500 #consul服务器的ip地址discovery:#是否需要注册register: true#注册的实例ID (唯一标志)instance-id: ${spring.application.name}-1#服务的名称service-name: ${spring.application.name}#服务的请求端口port: ${server.port}#指定开启ip地址注册prefer-ip-address: true#当前服务的请求ipip-address: ${spring.cloud.client.ip-address}

在服务消费者的启动器上@LoadBalanced,Ribbon的负载均衡支持

@SpringBootApplication
@EntityScan("cn.runze.order.entity")
public class OrderApplication {/*** springcloud对consul进行了进一步的处理*  向其中集成了ribbon的支持*/@LoadBalanced@Beanpublic RestTemplate restTemplate() {return new RestTemplate();}public static void main(String[] args) {SpringApplication.run(OrderApplication.class,args);}
}

在调用服务的地方直接写服务名称即可调用

package cn.runze.order.controller;import cn.runze.order.entity.Product;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;@RestController
@RequestMapping("/order")
public class OrderController {@Autowiredprivate RestTemplate restTemplate;@RequestMapping(value = "/buy/{id}",method = RequestMethod.GET)public Product findById(@PathVariable Long id) {Product product = restTemplate.getForObject("http://service-product/product/1",Product.class);;return product;}}
2.3.2 Consul高可用集群

暂时没法演示

2.3 Feign

Feign是Netflix开发的声明式,模板化的HTTP客户端,其灵感来自Retrofit,JAXRS-2.0以及WebSocket.

  • Feign可帮助我们更加便捷,优雅的调用HTTP API。
  • 在SpringCloud中,使用Feign非常简单——创建一个接口,并在接口上添加一些注解,代码就完成了。
  • Feign支持多种注解,例如Feign自带的注解或者JAX-RS注解等。
  • SpringCloud对Feign进行了增强,使Feign支持了SpringMVC注解,并整合了Ribbon和Eureka,
    从而让Feign的使用更加方便。
2.3.1 Feign入门
  1. 导入依赖
 <!--在消费者的pom文件上添加feign依赖--><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-openfeign</artifactId></dependency>
  1. 配置调用接口
package com.runze.order.feign;import com.runze.order.domain.Product;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;/*** 生命需要调用的微服务名称*  @FeignClient*      *name:调用的服务的名称*/@FeignClient("service-product")
@Component
public interface ProductFeignClient {/*** 配置需要调用的微服务接口*/@RequestMapping(value="/product/{id}",method= RequestMethod.GET)public Product findById(@PathVariable("id") Long id);}
  1. 在启动类上激活feign
@SpringBootApplication
@EntityScan("com.runze.order.domain")
@EnableEurekaClient
@EnableFeignClients
  1. 通过自动的接口调用远程微服务
package com.runze.order.controller;import com.runze.order.domain.Product;
import com.runze.order.feign.ProductFeignClient;
import net.bytebuddy.asm.Advice;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;import java.util.List;@RestController
@RequestMapping("/order")
public class OrderController {@Autowiredprivate ProductFeignClient feignClient;//使用Ribbon进行远程调用@GetMapping("/buy/{id}")public Product findProductById(@PathVariable Long id){Product product = feignClient.findById(id);return product;}
2.3.2 Feign的负载均衡

以轮询的方式进行访问服务提供者

2.3.3 feign配置日志输出

在服务调用者的配置文件中配置如下内容即可


# 配置feign的日志输出
# 日志级别NONE:不输出日志 BASIC:适用于生产环境追踪问题 HEADERS:在BASIC的基础上,记录请求和响应头问题 FULL:记录所有
feign:client:config:service-product:loggerlevel: FULL
logging:level:com.runze.order.feign.ProductFeignClient: debug
2.3.3 feign源码分析

2.4高并发问题

2.4.1使用JMeter模拟高并发情况

如何解决由于请求积压造成的服务崩溃问题:服务隔离的方式,具体如下图
服务隔离分为两类:

  1. 线程池隔离:就是对多个服务单独创建线程池,防止由于某个服务访问量过多导致其他服务无法使用的问题。
  2. 信号量隔离:实际就是一个计数器,设置某个服务的最大访问量,如果超出这个阈值,就会直接报错,无法访问。
2.4.2使用线程池隔离解决某一服务访问量过大的问题
  1. 导入坐标
       <dependency><groupId>com.netflix.hystrix</groupId><artifactId>hystrix-metrics-event-stream</artifactId><version>1.5.12</version></dependency><dependency><groupId>com.netflix.hystrix</groupId><artifactId>hystrix-javanica</artifactId><version>1.5.12</version></dependency>

2.创建OrderCommand类,设置线程池参数

package com.runze.order.command;import com.netflix.hystrix.*;
import com.runze.order.domain.Product;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.client.RestTemplate;public class OrderCommand extends HystrixCommand<Product> {private RestTemplate restTemplate;private Long id;public OrderCommand(RestTemplate restTemplate, Long id) {super(setter());this.restTemplate = restTemplate;this.id = id;}private static Setter setter() {// 服务分组HystrixCommandGroupKey groupKey = HystrixCommandGroupKey.Factory.asKey("order_product");// 服务标识HystrixCommandKey commandKey = HystrixCommandKey.Factory.asKey("product");// 线程池名称HystrixThreadPoolKey threadPoolKey = HystrixThreadPoolKey.Factory.asKey("order_product_pool");/*** 线程池配置*     withCoreSize :  线程池大小为10*     withKeepAliveTimeMinutes:  线程存活时间15秒*     withQueueSizeRejectionThreshold  :队列等待的阈值为100,超过100执行拒绝策略*/HystrixThreadPoolProperties.Setter threadPoolProperties = HystrixThreadPoolProperties.Setter().withCoreSize(50).withKeepAliveTimeMinutes(15).withQueueSizeRejectionThreshold(100);// 命令属性配置Hystrix 开启超时HystrixCommandProperties.Setter commandProperties = HystrixCommandProperties.Setter()// 采用线程池方式实现服务隔离.withExecutionIsolationStrategy(HystrixCommandProperties.ExecutionIsolationStrategy.THREAD)// 禁止.withExecutionTimeoutEnabled(false);return Setter.withGroupKey(groupKey).andCommandKey(commandKey).andThreadPoolKey(threadPoolKey).andThreadPoolPropertiesDefaults(threadPoolProperties).andCommandPropertiesDefaults(commandProperties);}@Overrideprotected Product run() throws Exception {System.out.println(Thread.currentThread().getName());return restTemplate.getForObject("http://service-product/product/1",Product.class);}@Overrideprotected Product getFallback(){Product product=new Product();product.setProductName("不好意思,出错了");return product;
}
}

3.在OrderController中调用OrderCommand的方法

        System.out.println(Thread.currentThread().getName());return new OrderCommand(restTemplate, id).execute();

4.测试,使用JMeter模拟过量访问findProductById,同时在浏览器端访问Order服务的另一个方法findById,会发现访问findProductById与访问findById的不是同一线程

SpringCloud01相关推荐

  1. SpringCloud学习笔记day01

    SpringCloud01 1.认识微服务 随着互联网行业的发展,对服务的要求也越来越高,服务架构也从单体架构逐渐演变为现在流行的微服务架构.这些架构之间有怎样的差别呢? 1.0.学习目标 了解微服务 ...

  2. springCloud之Netflix完整学习

    SpringCloud 是一个生态,不是一门技术,用来解决下面的问题 微服务的4个核心问题: 服务很多,客户如何访问? 这么多服务,服务之间怎么通信? 服务太多了,如何治理? 服务挂了怎么办? 解决方 ...

  3. Spring Cloud学习资料01

    SpringCloud01 1.认识微服务 随着互联网行业的发展,对服务的要求也越来越高,服务架构也从单体架构逐渐演变为现在流行的微服务架构.这些架构之间有怎样的差别呢? 1.0.学习目标 了解微服务 ...

  4. 微服务01SpringCloud Eureka Ribbon Nacos Feign Gateway服务网关

    微服务技术栈导学 SpringCloud01 1.认识微服务 随着互联网行业的发展,对服务的要求也越来越高,服务架构也从单体架构逐渐演变为现在流行的微服务架构.这些架构之间有怎样的差别呢? 1.0.学 ...

  5. 关于SpringCloud,我肝了7万字

    目录 概述 一.业务场景介绍 微服务cloud整体聚合父工程Project 父工程的pom 子模块 微服务提供者支付模块:cloud-provider-payment8001 微服务消费者订单模块cl ...

  6. Hystrix 集群 及 集群监控 Turbine

    Hystrix 集群 及 集群监控 turbine Hystrix 集群及监控 turbine Feign.Hystrix整合 集群后超时设置 本章知识: 1.Hystrix集群及监控turbine ...

  7. SpringCloud学习笔记01——Eureka 和 Nacos注册

    SpringCloud01 1.认识微服务 随着互联网行业的发展,对服务的要求也越来越高,服务架构也从单体架构逐渐演变为现在流行的微服务架构.这些架构之间有怎样的差别呢? 1.0.学习目标 了解微服务 ...

最新文章

  1. 操作系统结构-外核结构
  2. 实现控制台上的进度条
  3. python连接mongodb进行查询_Python中的MongoDB基本操作:连接、查询实例
  4. Java中单链表的实现
  5. java中的数据结构总结
  6. 国开mysql答案_国开MySQL数据库应用形考任务.doc
  7. 父类可以调用子类的方法吗_python类的继承、多继承及查找方法顺序
  8. 前端学习(3246):react的生命周期getSnap
  9. 马斯克:特斯拉智能召唤功能已被使用超过55万次
  10. php有没有dao层,php框架开发四(DAO层)_PHP教程
  11. Python批处理MODIS数据并计算NDVI
  12. 电阻、电容、电感、半导体器件的失效分析
  13. 基本知识 100151
  14. Mode首席执行官Paul Dawes:从销售工程师到科技领导者
  15. 数据库expecting ''', found 'EOF'异常——原载于我的百度空间
  16. 在ios10+的safair中实现视频的自动播放
  17. 【DP】【高精】WZK打雪仗
  18. 华三comware跳槽_小灰的网工日常之华三瘦转胖AP,我与华三的又一段孽缘呀~~~
  19. react 函数组件暴露方法
  20. 圣诞节营销攻略之贺卡、逼单、开发的模板

热门文章

  1. Pacemaker,Corosync和PCS搭建高可用性负载均衡linux 集群(httpd)
  2. SEO如何建立网站关键词词库?
  3. 第五次网页前端培训笔记(JS)
  4. 1211877-36-9,Z-L-DLeu-L-CHO
  5. SQL Server 练习题3
  6. 谷歌开发客户工具总结
  7. keepAlive使用方法
  8. 软件测试-黑盒测试学习
  9. OpenWrt 定时关闭2.4g或5g wifi
  10. 能量原理与变分法笔记07: 多个自变函数的变分问题+条件极值问题的变分法+第一章的思考