前言:今天看到周阳老师出了新课,十分欣喜,很喜欢周阳老师的讲课风格,内容也充实,我也算是周阳老师忠实粉丝啦。

新出的springcloud第二版很符合我现阶段的学习需求。但美中不足的是,目前只有视频资料,并没有周阳老师上课的脑图,一点一点跟着老师构建项目,稍有吃力。将目前学习的笔记整理,供自己日后复习,供大家参考。若有不足,还请指正。

周阳微服务架构与SpringCloud

关于其他博客的配置或多或少跟老师课程有出入,所以真的是踩了无数的坑,现在将我学习过程中的资料粘贴出来,大部分我都跟老师的视频一一比对过。因为大部分都是环境,pom文件、配置文件不对的问题,希望可以帮助到大家。
我的可能很多端口号和视频不一致,这是因为本地被占用,不能停止服务,所以换了端口号,只要端口号正确就不会不影响运行。


!!!!!配置文件如果使用的是yml文件,千万要注意格式 层次和空格
!!!!!配置文件如果使用的是yml文件,千万要注意格式 层次和空格
!!!!!配置文件如果使用的是yml文件,千万要注意格式 层次和空格


源码已分享在个人github:cloud2020不断更新,供参考,觉得有用的化给个star吧哈哈哈。

SpringCloud=分布式微服务架构的一站式解决方案,是多种微服务架构落地技术的集合体,俗称微服务全家桶

SpringBoot是一种服务开发技术

服务注册与发现:EUREKA

服务负载均衡与调用:NETFLIX OSS RIBBON

服务负载与调用:NETTFLIX

服务熔断降级:HYSTRIX

服务网关:Zuul

服务分布式配置:SpringCloud Config

服务开发:SpingBoot

SpringBoot 2.0版和SpringCloud H版 强烈建议使用SpringBoot 2.0以上

SpringBoot和SpringCloud之间版本有约束 H版对应2.2 G版对应2.1

课程版本约束

cloud:Hoxton.SR1

boot:2.2.2.RELEASE

cloud alibaba:2.1.0.RELEASE

java:java8

Maven 3.5以上

Mysql:5.7以上

1、新建maven项目:cloud2020

项目结构目录如下:

pom.xml如下

<?xml version="1.0" encoding="UTF-8"?><project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>com.atguigu.springcloud</groupId><artifactId>cloud2020</artifactId><version>1.0-SNAPSHOT</version><packaging>pom</packaging><modules><module>cloud-provider-payment8001</module><module>cloud-consumer-order80</module><module>cloud-api-commons</module><module>cloud-api-commons</module><module>cloud-eureka-server7001</module></modules><!--统一管理jar包版本--><properties><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><maven.compiler.source>1.8</maven.compiler.source><maven.compiler.target>1.8</maven.compiler.target><junit.version>4.12</junit.version><log4j.version>1.2.17</log4j.version><lombok.version>1.16.18</lombok.version><mysql.version>8.0.18</mysql.version><druid.version>1.1.16</druid.version><mybatis.spring.boot.version>1.3.0</mybatis.spring.boot.version></properties><dependencyManagement><dependencies><!--springboot 2.2.2--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-dependencies</artifactId><version>2.2.2.RELEASE</version><type>pom</type><scope>import</scope></dependency><!--Spring cloud Hoxton.SR1--><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-dependencies</artifactId><version>Hoxton.SR1</version><type>pom</type><scope>import</scope></dependency><!--Spring cloud alibaba 2.1.0.RELEASE--><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-alibaba-dependencies</artifactId><version>2.1.0.RELEASE</version><type>pom</type><scope>import</scope></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>${mysql.version}</version></dependency><dependency><groupId>com.alibaba</groupId><artifactId>druid</artifactId><version>${druid.version}</version></dependency><dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>${mybatis.spring.boot.version}</version></dependency><dependency><groupId>com.alibaba</groupId><artifactId>druid-spring-boot-starter</artifactId><version>${druid.spring.boot.starter.version}</version></dependency><!--lombok--><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>${lombok.version}</version></dependency><!--junit--><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>${junit.version}</version></dependency><!-- log4j --><dependency><groupId>log4j</groupId><artifactId>log4j</artifactId><version>${log4j.version}</version></dependency></dependencies></dependencyManagement><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId><configuration><fork>true</fork><addResources>true</addResources></configuration></plugin></plugins></build></project>

2、创建数据库

CREATE DATABASE /*!32312 IF NOT EXISTS*/`cloud` /*!40100 DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci */;
​
USE `cloud`;
​
/*Table structure for table `payment` */
​
DROP TABLE IF EXISTS `payment`;
​
CREATE TABLE `payment` (`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'ID',`serial` varchar(200) COLLATE utf8_unicode_ci DEFAULT NULL,PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
​
/*Data for the table `payment` */
​
insert  into `payment`(`id`,`serial`) values (1,'尚硅谷'),(2,'alibaba'),(3,'京东'),(4,'头条');

3、resource目录下新建 application.yml

注意: mysql8的数据库驱动包是 com.mysql.cj.jdbc.Driver

mysql5是 com.mysql.jdbc.Driver

server:port: 8001spring:application:name: cloud-provider-servicedatasource:type: com.alibaba.druid.pool.DruidDataSource  #当前数据源操作类型driver-class-name: com.mysql.cj.jdbc.Driver        #mysql驱动包url: jdbc:mysql://localhost:3306/db2019?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=utf-8&useSSL=falseusername: rootpassword: rooteureka:client:register-with-eureka: true #表示向注册中心注册自己 默认为truefetch-registry: true #是否从EurekaServer抓取已有的注册信息,默认为true,单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡service-url:defaultZone: http://localhost:7011/eureka/mybatis:mapper-locations: classpath:mapper/*.xmltype-aliases-package: com.atguigu.springcloud.entities       #所有Entity别名类所在包

4、新建启动类PaymentMain8001

@SpringBootApplication
public class PaymentMain8001 {public static void main(String[] args) {SpringApplication.run(PaymentMain8001.class, args);}
}

5、dao层开发

新建PaymentDao接口

import com.atguigu.springcloud.entities.Payment;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;@Mapper
public interface PaymentDao {public int create(Payment payment);public Payment getById(@Param("id") Long id);
}

在resource下建立mapper文件夹用来存放mybatis的映射配置文件

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.atguigu.springcloud.dao.PaymentDao"><insert id="create" parameterType="Payment" useGeneratedKeys="true" keyProperty="id">insert into payment(serial) values(#{serial})</insert><resultMap id="BaseResultMap" type="com.atguigu.springcloud.entities.Payment"><id column="id" property="id" jdbcType="BIGINT"/><result column="serial" property="serial" jdbcType="VARCHAR"/></resultMap><select id="getById" parameterType="Long" resultMap="BaseResultMap">select * from payment where id=#{id};</select>
</mapper>

6、service层

service接口

import com.atguigu.springcloud.entities.Payment;
import org.apache.ibatis.annotations.Param;public interface PaymentService {public int create(Payment payment);public Payment getById(@Param("id") Long id);
}

service实现类


import com.atguigu.springcloud.dao.PaymentDao;
import com.atguigu.springcloud.entities.Payment;
import com.atguigu.springcloud.service.PaymentService;
import org.springframework.stereotype.Service;import javax.annotation.Resource;@Service
public class PaymentServiceImpl implements PaymentService {@Resourceprivate PaymentDao paymentDao;@Overridepublic int create(Payment payment) {return paymentDao.create(payment);}@Overridepublic Payment getById(Long id) {return paymentDao.getById(id);}
}

7、controller层

import com.atguigu.springcloud.entities.CommonResult;
import com.atguigu.springcloud.entities.Payment;
import com.atguigu.springcloud.service.PaymentService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;import javax.annotation.Resource;@RestController
@Slf4j
public class PaymentController {@Resourceprivate PaymentService paymentService;@PostMapping("/payment/create")public CommonResult create(@RequestBody  Payment payment){int result = paymentService.create(payment);log.info("**********插入结果:"+result);if(result>0){return new CommonResult(200,"插入数据成功",result);}else{return new CommonResult(444,"插入数据失败",null);}}@GetMapping("/payment/get/{id}")public CommonResult getPaymentById(@PathVariable("id") Long id){Payment payment = paymentService.getById(id);log.info("**********查询结果:"+payment);if(payment != null){return new CommonResult(200,"查询数据成功",payment);}else{return new CommonResult(444,"查询数据失败",null);}}
}

8、测试

1、get测试:浏览器输入:http://localhost:8001/payment/get/2

结果:{"code":200,"message":"查询数据成功","data":{"id":2,"serial":"alibaba"}}

2、post测试:

二、cloud-consumer-order80

1、pom文件中添加依赖

<artifactId>cloud-consumer-order80</artifactId><description>订单消费者</description><dependencies><!--eureka-client--><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-netflix-eureka-client</artifactId></dependency><dependency><!--引用自己定义的api通用包,可以使用payment支付entity--><groupId>com.atguigu.springcloud</groupId><artifactId>cloud-api-commons</artifactId><version>${project.version}</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-actuator</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-devtools</artifactId><scope>runtime</scope><optional>true</optional></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId></dependency></dependencies>

2、application.yml

server:port: 8001

3、启动类(同上个模块省略)

4、拷贝实体类、

5、RestTemplate

RestTemplate提供了多种便捷访问远程Http服务的方法,

是一种简单便捷的访问restful服务的模板类,是spring提供的用于访问Rest服务的客户端模板工具集。

配置类

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;@Configuration
public class ApplicationContextConfig {@Beanpublic RestTemplate getRestTemplate(){return new RestTemplate();}
}
//相当于applicationContext.xml  <bean id="" class = "">

6、controller层

import com.atguigu.springcloud.entities.CommonResult;
import com.atguigu.springcloud.entities.Payment;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;import javax.annotation.Resource;@RestController
@Slf4j
public class OrderController {public static final  String PAYMENT_URL = "http://localhost:8001";@Resourceprivate RestTemplate restTemplate;@GetMapping("/consumer/payment/create")public CommonResult<Payment> create(Payment payment){return restTemplate.postForObject(PAYMENT_URL+"/payment/create",payment,CommonResult.class);}@GetMapping("/consumer/payment/get/{id}")public CommonResult<Payment> getPayment(@PathVariable("id") Long id){return restTemplate.getForObject(PAYMENT_URL+"/payment/get/"+id,CommonResult.class);}
}

7、rundashbroad

我的idea没有了Run Dashboard,可能是版本问题,取而代之的是services ![在这里插入图片描述](https://img-blog.csdnimg.cn/20200702184321952.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0xpemVqaW45NjEwMTk=,size_16,color_FFFFFF,t_70)

运用spring cloud框架基于spring boot构建微服务,一般需要启动多个应用程序,在idea开发工具中,多个同时启动的应用

需要在Run Dashboard运行仪表盘中可以更好的管理,但有时候idea中的RunDashboard窗口没有显示出来,也找不到直接的开启按钮

idea中打开Run Dashboard的方法如下

    view > Tool Windows > Run Dashboard

如果上述列表找不到Run Dashboard,则可以在工程目录下找到.idea文件夹下的workspace.xml,在其中相应位置加入以下代码(替换)即可:

<component name="RunDashboard">
<option name="configurationTypes"><set><option value="SpringBootApplicationConfigurationType"/></set>
</option>
<option name="ruleStates"><list><RuleState><option name="name" value="ConfigurationTypeDashboardGroupingRule"/></RuleState><RuleState><option name="name" value="StatusDashboardGroupingRule"/></RuleState></list>
</option>
</component>

关闭重启后出现。

三、工程重构

1、新建模块 cloud-api-commons

依赖:

<artifactId>cloud-api-commons</artifactId><description>公共模块</description><dependencies><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-devtools</artifactId><scope>runtime</scope><optional>true</optional></dependency><dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId><version>5.1.0</version></dependency></dependencies>

2、将消费者和服者种的entities拷贝至新模块中,删除原来的entities包,clean、install cloud-api-commons 模块,在消费则服务者pom.xml中分别引入依赖,测试运行。

四、Eureka

1、Eureka是什么

Eureka 是 Netflix 开发的,一个基于 REST 服务的,服务注册与发现的组件,以实现中间层服务器的负载平衡和故障转移。

它主要包括两个组件:Eureka Server 和 Eureka Client

  • Eureka Client:一个Java客户端,用于简化与 Eureka Server 的交互(通常就是微服务中的客户端和服务端)

  • Eureka Server:提供服务注册和发现的能力(通常就是微服务中的注册中心)

    ![在这里插入图片描述](https://img-blog.csdnimg.cn/20200701101354363.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0xpemVqaW45NjEwMTk=,size_16,color_FFFFFF,t_70#pic_center)

服务在Eureka上注册,然后每隔30秒发送心跳来更新它们的租约。如果客户端不能多次续订租约,那么它将在大约90秒内从服务器注册表中剔除。注册信息和更新被复制到集群中的所有eureka节点。来自任何区域的客户端都可以查找注册表信息(每30秒发生一次)来定位它们的服务(可能在任何区域)并进行远程调用

2、 Eureka 客户端与服务器之间的通信

服务发现有两种模式:一种是客户端发现模式,一种是服务端发现模式。Eureka采用的是客户端发现模式。

2.1. Register(注册)

Eureka客户端将关于运行实例的信息注册到Eureka服务器。注册发生在第一次心跳。

2.2. Renew(更新 / 续借)

Eureka客户端需要更新最新注册信息(续借),通过每30秒发送一次心跳。更新通知是为了告诉Eureka服务器实例仍然存活。如果服务器在90秒内没有看到更新,它会将实例从注册表中删除。建议不要更改更新间隔,因为服务器使用该信息来确定客户机与服务器之间的通信是否存在广泛传播的问题。

2.3. Fetch Registry(抓取注册信息)

Eureka客户端从服务器获取注册表信息并在本地缓存。之后,客户端使用这些信息来查找其他服务。通过在上一个获取周期和当前获取周期之间获取增量更新,这些信息会定期更新(每30秒更新一次)。获取的时候可能返回相同的实例。Eureka客户端自动处理重复信息。

2.4. Cancel(取消)

Eureka客户端在关机时向Eureka服务器发送一个取消请求。这将从服务器的实例注册表中删除实例,从而有效地将实例从流量中取出。

3、Eureka自我保护模式

如果 Eureka 服务器检测到超过预期数量的注册客户端以一种不优雅的方式终止了连接,并且同时正在等待被驱逐,那么它们将进入自我保护模式。这样做是为了确保灾难性网络事件不会擦除eureka注册表数据,并将其向下传播到所有客户端。

任何客户端,如果连续3次心跳更新失败,那么它将被视为非正常终止,病句将被剔除。当超过当前注册实例15%的客户端都处于这种状态,那么自我保护将被开启。

当自我保护开启以后,eureka服务器将停止剔除所有实例,直到:

  1. 它看到的心跳续借的数量回到了预期的阈值之上,或者

  2. 自我保护被禁用

默认情况下,自我保护是启用的,并且,默认的阈值是要大于当前注册数量的15%

4、Eureka VS Zookeeper

4.1. Eureka保证AP

Eureka服务器节点之间是对等的,只要有一个节点在,就可以正常提供服务。

Eureka客户端的所有操作可能需要一段时间才能在Eureka服务器中反映出来,随后在其他Eureka客户端中反映出来。也就是说,客户端获取到的注册信息可能不是最新的,它并不保证强一致性

4.2. Zookeeper保证CP

Zookeeper集群中有一个Leader,多个Follower。Leader负责写,Follower负责读,ZK客户端连接到任何一个节点都是一样的,写操作完成以后要同步给所有Follower以后才会返回。如果Leader挂了,那么重新选出新的Leader,在此期间服务不可用。

4.3. 为什么用Eureka

分布式系统大都可以归结为两个问题:数据一致性和防止单点故障。而作为注册中心的话,即使在一段时间内不一致,也不会有太大影响,所以在A和C之间选择A是比较适合该场景的。

工程实现步骤

1、新建cloud-eureka-server7001模块

pom.xml中加入依赖:

<dependencies><!--eureka-server--><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-netflix-eureka-server</artifactId></dependency><!--自定义api通用包--><dependency><groupId>com.atguigu.springcloud</groupId><artifactId>cloud-api-commons</artifactId><version>${project.version}</version></dependency><!--boot web acctuator--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-actuator</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-devtools</artifactId><scope>runtime</scope><optional>true</optional></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><dependency><groupId>junit</groupId><artifactId>junit</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-autoconfigure</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot</artifactId></dependency></dependencies>

2、resources目录下新建application.yml

server:port: 7001eureka:instance:# eureka服务端的实例名称hostname: localhostclient:# false表示不向注册中心注册自己register-with-eureka: false# false表示自己端就是注册中心,我的职责就是维护服务实例,并不需要检索服务fetch-registry: falseservice-url:defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/

3、编写启动类EurekaApplication7001


import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;@SpringBootApplication
@EnableEurekaServer
public class EurekaApplication7001 {public static void main(String[] args) {SpringApplication.run(EurekaApplication7001.class,args);}
}

2、支付微服务8001入驻7001

​ 1、pom.xml添加

<!--eureka-server-->
<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

​ 2、application.yml

server:port: 8001spring:application:name: cloud-payment-servicedatasource:type: com.alibaba.druid.pool.DruidDataSource  #当前数据源操作类型driver-class-name: com.mysql.cj.jdbc.Driver        #mysql驱动包url: jdbc:mysql://localhost:3306/db2019?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=utf-8&useSSL=falseusername: rootpassword: rooteureka:client:register-with-eureka: true #表示向注册中心注册自己 默认为truefetch-registry: true #是否从EurekaServer抓取已有的注册信息,默认为true,单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡service-url:defaultZone: http://localhost:7001/eureka/
mybatis:mapper-locations: classpath:mapper/*.xmltype-aliases-package: com.atguigu.springcloud.entities       #所有Entity别名类所在包

​ 3、启动类加上注解@EnableEurekaClient

3、订单微服务80入驻7001

​ 同上

五、Eureka集群

原理说明:

服务注册:将服务信息注册到注册中心

服务发现:从注册中心获取服务信息

实质:存key服务名,取value调用地址

步骤:

  1. 先启动eureka注册中心

  2. 启动服务提供者payment支付服务

  3. 支付服务启动后,会把自身信息注册到eureka

  4. 消费者order服务在需要调用接口时,使用服务别名去注册中心获取实际的远程调用地址

  5. 消费者获得调用地址后,底层实际是调用httpclient技术实现远程调用

  6. 消费者获得服务地址后会缓存在本地jvm中,默认每30秒更新异常服务调用地址

问题:微服务RPC远程调用最核心的是说明?

高可用,如果注册中心只有一个,出现故障就麻烦了。会导致整个服务环境不可用。

解决办法:搭建eureka注册中心集群,实现负载均衡+故障容错

互相注册,相互守望

集群搭建步骤

1、依照7001新建7002,除了主启动类和yml配置文件按,其他都一样

2、修改C:\Windows\System32\drivers\etc下的hosts

末尾加上

推荐一个小工具 switchhost。大家自行百度吧 ,挺方便的 ```java # springcloud2020 127.0.0.1 eureka7001.com 127.0.0.1 eureka7002.com 127.0.0.1 eureka7003.com ```

3、修改7001项目 applicaton.yml

server:port: 7001
​
eureka:instance:hostname: eureka7001.com #eureka服务端实例名称client:register-with-eureka: false #表示不向注册中心注册自己fetch-registry: false #false表示自己就是注册中心,我的职责就是维护服务实例,并不区检索服务service-url:defaultZone: http://eureka7002.com:7002/eureka/

4、修改7002

server:port: 7002
​
eureka:instance:hostname: eureka7002.com #eureka服务端实例名称client:register-with-eureka: false #表示不向注册中心注册自己fetch-registry: false #false表示自己就是注册中心,我的职责就是维护服务实例,并不区检索服务service-url:defaultZone: http://eureka7001.com:7001/eureka/

5、eurekaserver集群效果

6、支付和订单两个微服务注册到eureka集群

修改80项目yml配置文件

service-url:#defaultZone: http://localhost:7001/eureka/ # 入驻地址defaultZone: http://eureka7001.com:7001/eureka/, http://eureka7001.com:7001/eureka/

8001同理

启动7001、7002再启动8001、80项目查看效果

六、搭建支付服务集群

参照8001搭建8002服务

访问localhost:8001/payment/get/1

结果:{"code":200,"message":"查询数据成功,serverport:8001","data":{"id":1,"serial":"尚硅谷"}}

访问localhost:8002/payment/get/1

结果:{"code":200,"message":"查询数据成功,serverport:8002","data":{"id":1,"serial":"尚硅谷"}}

成功

访问localhost/consumer/payment/get/1

结果:{"code":200,"message":"查询数据成功,serverport:8001","data":{"id":1,"serial":"尚硅谷"}}

但是会发现 ,每次范文都是8001端口

原因是再80项目的controller层中,我们将请求路径写死了。

修改如下:

public class OrderController {​//    private final static String PAYMENT_URL = "http://localhost:8001";private final static String PAYMENT_URL = "http://CLOUD-PAYMENT-SERVICE";

但是访问localhost/consumer/payment/get/1会出现500错误

报错信息如下:

There was an unexpected error (type=Internal Server Error, status=500).
I/O error on GET request for "http://CLOUD-PAYMENT-SERVICE/payment/get/1": CLOUD-PAYMENT-SERVICE; nested exception is java.net.UnknownHostException: CLOUD-PAYMENT-SERVICE
org.springframework.web.client.ResourceAccessException: I/O error on GET request for "http://CLOUD-PAYMENT-SERVICE/payment/get/1": CLOUD-PAYMENT-SERVICE; nested exception is java.net.UnknownHostException: CLOUD-PAYMENT-SERVICE

原因是,我们配置了以服务名的方式访问,但不能确定是哪一个服务。

我们需要给restTemplate开启负载均衡,默认是轮循。

@Configuration
public class ApplicationContextConfig {​@Bean@LoadBalanced//开启负载均衡public RestTemplate getRestTemplate() {return new RestTemplate();}
}

我们看一下@LoadBalanced源码

/*** Annotation to mark a RestTemplate bean to be configured to use a LoadBalancerClient* @author Spencer Gibb*/
@Target({ ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Qualifier
public @interface LoadBalanced {}

可以看到 @LoadBalanced是一个注解,用于标记要配置为使用LoadBalancerClient的RestTemplate bean的。

那么我们再来看看 LoadBalancerClient

public interface ServiceInstanceChooser {ServiceInstance choose(String serviceId);
}
public interface LoadBalancerClient extends ServiceInstanceChooser {​<T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException;
​<T> T execute(String serviceId, ServiceInstance serviceInstance, LoadBalancerRequest<T> request) throws IOException;
​URI reconstructURI(ServiceInstance instance, URI original);
}

LoadBalancerClient是一个接口,该接口中有四个方法,我们来大概看一下这几个方法的作用:

  1. ServiceInstance choose(String serviceId)根据传入的服务名serviceId从客户端负载均衡器中挑选一个对应服务的实例。

  2. T execute() ,使用从负载均衡器中挑选出来的服务实例来执行请求。

  3. URI reconstructURI(ServiceInstance instance, URI original)表示为系统构建一个合适的URI,我们在Spring Cloud中服务的发现与消费一文中发送请求时使用了服务的逻辑名称(http://HELLO-SERVICE/hello)而不是具体的服务地址,在reconstructURI方法中,第一个参数ServiceInstance实例是一个带有host和port的具体服务实例,第二个参数URI则是使用逻辑服务名定义为host和port的URI,而返回的URI则是通过ServiceInstance的服务实例详情拼接出的具体的host:port形式的请求地址。一言以蔽之,就是把类似于http://HELLO-SERVICE/hello这种地址转为类似于http://195.124.207.128/hello地址(IP地址也可能是域名)。

六、搭建支付服务集群

参照8001搭建8002服务

访问localhost:8001/payment/get/1

结果:{"code":200,"message":"查询数据成功,serverport:8001","data":{"id":1,"serial":"尚硅谷"}}

访问localhost:8002/payment/get/1

结果:{"code":200,"message":"查询数据成功,serverport:8002","data":{"id":1,"serial":"尚硅谷"}}

成功

访问localhost/consumer/payment/get/1

结果:{"code":200,"message":"查询数据成功,serverport:8001","data":{"id":1,"serial":"尚硅谷"}}

但是会发现 ,每次范文都是8001端口

原因是再80项目的controller层中,我们将请求路径写死了。

修改如下:

public class OrderController {​//    private final static String PAYMENT_URL = "http://localhost:8001";private final static String PAYMENT_URL = "http://CLOUD-PAYMENT-SERVICE";

但是访问localhost/consumer/payment/get/1会出现500错误

报错信息如下:

There was an unexpected error (type=Internal Server Error, status=500).
I/O error on GET request for "http://CLOUD-PAYMENT-SERVICE/payment/get/1": CLOUD-PAYMENT-SERVICE; nested exception is java.net.UnknownHostException: CLOUD-PAYMENT-SERVICE
org.springframework.web.client.ResourceAccessException: I/O error on GET request for "http://CLOUD-PAYMENT-SERVICE/payment/get/1": CLOUD-PAYMENT-SERVICE; nested exception is java.net.UnknownHostException: CLOUD-PAYMENT-SERVICE

原因是,我们配置了以服务名的方式访问,但不能确定是哪一个服务。

我们需要给restTemplate开启负载均衡,默认是轮循。

@Configuration
public class ApplicationContextConfig {​@Bean@LoadBalanced//开启负载均衡public RestTemplate getRestTemplate() {return new RestTemplate();}
}

我们看一下@LoadBalanced源码

/*** Annotation to mark a RestTemplate bean to be configured to use a LoadBalancerClient* @author Spencer Gibb*/
@Target({ ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Qualifier
public @interface LoadBalanced {
}
​

可以看到 @LoadBalanced是一个注解,用于标记要配置为使用LoadBalancerClient的RestTemplate bean的。

那么我们再来看看 LoadBalancerClient

public interface ServiceInstanceChooser {ServiceInstance choose(String serviceId);
}
public interface LoadBalancerClient extends ServiceInstanceChooser {
​<T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException;
​<T> T execute(String serviceId, ServiceInstance serviceInstance, LoadBalancerRequest<T> request) throws IOException;
​URI reconstructURI(ServiceInstance instance, URI original);
}

LoadBalancerClient是一个接口,该接口中有四个方法,我们来大概看一下这几个方法的作用:

  1. ServiceInstance choose(String serviceId)根据传入的服务名serviceId从客户端负载均衡器中挑选一个对应服务的实例。

  2. T execute() ,使用从负载均衡器中挑选出来的服务实例来执行请求。

  3. URI reconstructURI(ServiceInstance instance, URI original)表示为系统构建一个合适的URI,我们在Spring Cloud中服务的发现与消费一文中发送请求时使用了服务的逻辑名称(http://HELLO-SERVICE/hello)而不是具体的服务地址,在reconstructURI方法中,第一个参数ServiceInstance实例是一个带有host和port的具体服务实例,第二个参数URI则是使用逻辑服务名定义为host和port的URI,而返回的URI则是通过ServiceInstance的服务实例详情拼接出的具体的host:port形式的请求地址。一言以蔽之,就是把类似于http://HELLO-SERVICE/hello这种地址转为类似于http://195.124.207.128/hello地址(IP地址也可能是域名)。

七、Eureka的自我保护机制

为什么会产生自我保护机制?

为防止EurekaClient可以正常运行,但是与EurekaServer网络不同的情况下,EurekaServer不会立刻将EurekaClient服务剔除。

什么是自我保护机制?

默认情况下,当Eureka server在一定时间内没有收到实例的心跳,便会把该实例从注册表中删除(默认是90秒),但是,如果短时间内丢失大量的实例心跳,便会触发eureka server的自我保护机制。

比如在开发测试时,需要频繁地重启微服务实例,但是我们很少会把eureka server一起重启(因为在开发过程中不会修改eureka注册中心),当一分钟内收到的心跳数大量减少时,会触发该保护机制。可以在eureka管理界面看到Renews threshold和Renews(last min),当后者(最后一分钟收到的心跳数)小于前者(心跳阈值)的时候,触发保护机制,会出现红色的警告:

EMERGENCY!EUREKA MAY BE INCORRECTLY CLAIMING INSTANCES ARE UP WHEN THEY'RE NOT.RENEWALS ARE LESSER THAN THRESHOLD AND HENCE THE INSTANCES ARE NOT BEGING EXPIRED JUST TO BE SAFE.

从警告中可以看到,eureka认为虽然收不到实例的心跳,但它认为实例还是健康的,eureka会保护这些实例,不会把它们从注册表中删掉。

在自我保护模式中,EurekaServer会保护服务注册表中的信息,不再注销任何服务实例。

综上,自我保护模式是一种应对网络异常的安全保护措施它的架构哲学是宁可同时保留所有微服务,也不忙保姆注销如何健康的微服务,使用自我保护模式,可以让Eureka集群更加健壮,稳定。

署于CAP 的AP分支。

如何禁止自我保护机制

服务提供者:

lease-renewal-interval-in-seconds: 1 # eureka客户端向服务端发送心跳的时间间隔 单位秒 默认30
lease-expiration-duration-in-seconds: 2 # eureka

注册中心配置:

server:enable-self-preservation: false # 关闭自我保护机制 保证不可用服务及时清除eviction-interval-timer-in-ms: 2000

springboot 整合zookeeper

1、pom.xml中添加依赖

注意 添加的zookeeper版本要与自己安装在服务器上的一致。

<dependencies><dependency><groupId>com.atguigu.springcloud</groupId><artifactId>cloud-api-commons</artifactId><version>${project.version}</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!--SpringBoot整合Zookeeper客户端--><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-zookeeper-discovery</artifactId><exclusions><!--先排除自带的zookeeper3.5.3--><exclusion><groupId>org.apache.zookeeper</groupId><artifactId>zookeeper</artifactId></exclusion></exclusions></dependency><!--添加zookeeper3.4.6版本 --><dependency><groupId>org.apache.zookeeper</groupId><artifactId>zookeeper</artifactId><version>3.4.6</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-actuator</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-devtools</artifactId><scope>runtime</scope><optional>true</optional></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency>
</dependencies>

2、配置yml文件

server:port: 8004
spring:application:name: cloud-provider-paymentcloud:zookeeper:connect-string: 118.178.91.123:2181

3、编写控制层代码

/*** @ClassName: PaymentController* @description:* @author: XZQ* @create: 2020/3/6 15:45**/
@RestController
@RequestMapping("/payment")
public class PaymentController {
​@Value("${server.port}")private String SERVER_PORT;
​@RequestMapping("/zk")public String paymentZK() {return "springcloud with zookeeper :" + SERVER_PORT + "\t" + UUID.randomUUID().toString();}
}
​

问题:zookeeper中的节点是持久还是临时的?

:临时的。

九、consul

Consul是什么

Consul是一个服务网格(微服务间的 TCP/IP,负责服务之间的网络调用、限流、熔断和监控)解决方案,它是一个一个分布式的,高度可用的系统,而且开发使用都很简便。它提供了一个功能齐全的控制平面,主要特点是:服务发现、健康检查、键值存储、安全服务通信、多数据中心。

与其它分布式服务注册与发现的方案相比,Consul 的方案更“一站式”——内置了服务注册与发现框架、分布一致性协议实现、健康检查、Key/Value 存储、多数据中心方案,不再需要依赖其它工具。Consul 本身使用 go 语言开发,具有跨平台、运行高效等特点,也非常方便和 Docker 配合使用。

十、Eureka、Zookeeper、Consul三个注册中心的异同点

组件名 语言 健康检查 对外暴露接口 CAP Spring Cloud 集成
Eureka Java 可配支持 HTTP AP 集成
Consul Go 支持 HTTP/DFS CP 集成
Zookeeper java 支持 客户端 CP 集成

十一、Ribbon

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

实现负载均衡的算法。

负载规则替换,注意,不能与主启动类在同一个包下!

package com.xzq.myrule;
​
import com.netflix.loadbalancer.IRule;
import com.netflix.loadbalancer.RandomRule;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
​
/*** @ClassName: MySelfRule **/
@Configuration
public class MySelfRule {
​@Beanpublic IRule myRule() {return new RandomRule();}
}
​

主启动类添加注释:

@RibbonClient(name = "CLOUD-PAYMENT-SERVICE", configuration = MySelfRule.class)

作用是替换ribbon负载均衡规则

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

ribbon源码

private int incrementAndGetModulo(int modulo) {int current;int next;do {current = this.nextServerCyclicCounter.get();next = (current + 1) % modulo;} while(!this.nextServerCyclicCounter.compareAndSet(current, next));
​return next;
}

手写一个负载的算法CAS+自旋锁

首先8001、8002服务controller层加上

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

LoadBalancer接口:

package com.xzq.springcloud.lb;
​
import org.springframework.cloud.client.ServiceInstance;
​
import java.util.List;
​
/*** @InterfaceName: LoadBalancer* @description:* @author: XZQ* @create: 2020/3/7 15:55**/
public interface LoadBalancer {ServiceInstance instances(List<ServiceInstance> serviceInstances);
}
​

实现类:

package com.xzq.springcloud.lb;
​
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.stereotype.Component;
​
import java.sql.SQLOutput;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
​
/*** @ClassName: MyLB* @description:* @author: XZQ* @create: 2020/3/7 15:55**/
@Component
public class MyLB implements LoadBalancer {
​private AtomicInteger atomicInteger = new AtomicInteger(0);
​private final int getAndIncrement() {int current;int next;
​do {current = this.atomicInteger.get();next = current >= Integer.MAX_VALUE ? 0 : current + 1;} while (!atomicInteger.compareAndSet(current, next));System.out.println("第几次访问,次数next:" + next);return next;}
​@Overridepublic ServiceInstance instances(List<ServiceInstance> serviceInstances) {int index = getAndIncrement() % serviceInstances.size();return serviceInstances.get(index);}
}
​

controller类中添加:

@GetMapping("/consumer/payment/lb")
public String getPaymentLB() {List<ServiceInstance> 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);
}

十二、OpenFeign

OpenFeign是什么?

Feign是一个声明式的Web Service客户端。它的出现使开发Web Service客户端变得很简单。使用Feign只需要创建一个接口加上对应的注解,比如:FeignClient注解。Feign有可插拔的注解,包括Feign注解和JAX-RS注解。Feign也支持编码器和解码器,Spring Cloud Open Feign对Feign进行增强支持Spring MVC注解,可以像Spring Web一样使用HttpMessageConverters等。

Feign是一种声明式、模板化的HTTP客户端。在Spring Cloud中使用Feign,可以做到使用HTTP请求访问远程服务,就像调用本地方法一样的,开发者完全感知不到这是在调用远程方法,更感知不到在访问HTTP请求。

功能可插拔的注解支持,包括Feign注解和JAX-RS注解。支持可插拔的HTTP编码器和解码器(Gson,Jackson,Sax,JAXB,JAX-RS,SOAP)。支持Hystrix和它的Fallback。支持Ribbon的负载均衡。支持HTTP请求和响应的压缩。灵活的配置:基于 name 粒度进行配置支持多种客户端:JDK URLConnection、apache httpclient、okhttp,ribbon)支持日志支持错误重试url支持占位符可以不依赖注册中心独立运行

代码实现:

1、cloud-provider-payment8001模块controller层添加代码:

@GetMapping(value = "/feign/timeout")
public String paymentFeignTimeout() {try {// 暂停3秒钟 模拟超时任务TimeUnit.SECONDS.sleep(3);} catch (InterruptedException e) {e.printStackTrace();}return SERVER_PORT;
}

2、新建cloud-consumer-feign-order80模块

3、pom文件(注意将实体类依赖换成自己的!)

<dependencies><!--openfeign--><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-openfeign</artifactId></dependency><!--eureka client--><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-netflix-eureka-server</artifactId></dependency><dependency><groupId>com.atguigu.springcloud</groupId><artifactId>cloud-api-commons</artifactId><version>${project.version}</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!--监控--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-actuator</artifactId></dependency><!--热部署--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-devtools</artifactId><scope>runtime</scope><optional>true</optional></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency>
</dependencies>

4、yml配置

server:port: 80
​
eureka:client:register-with-eureka: truefetch-registry: trueservice-url:defaultZone: http://eureka7001.com:7001/eureka/, http://eureka7002.com:7002/eureka/ # 入驻地址

5、主启动类

/*** @ClassName: OrderFeignMain80* @description:* @author: XZQ* @create: 2020/3/7 17:21**/
@SpringBootApplication
@EnableEurekaClient
@EnableFeignClients//启用feign客户端
public class OrderFeignMain80 {public static void main(String[] args) {SpringApplication.run(OrderFeignMain80.class, args);}
}

6、service包下创建PaymentFeignService接口

/*** @ClassName: PaymentFeignService* @description:* @author: XZQ* @create: 2020/3/8 15:30**/
@Component
@FeignClient(value = "CLOUD-PAYMENT-SERVICE")
public interface PaymentFeignService {/*** 根据id查询** @param id* @return*/@GetMapping(value = "/payment/get/{id}")CommonResult getPaymentById(@PathVariable("id") Long id);
​/*** 模拟feign超时** @return*/@GetMapping(value = "/payment/feign/timeout")String paymentFeignTimeout();
}
​

7、controller层代码

/*** @ClassName: OrderFeignClientController* @description:* @author: XZQ* @create: 2020/3/8 15:28**/
@RestController
@RequestMapping("/consumer")
public class OrderFeignClientController {@Autowiredprivate PaymentFeignService paymentFeignService;
​/*** 根据id查询** @param id* @return*/@GetMapping(value = "/payment/get/{id}")public CommonResult getPaymentById(@PathVariable("id") Long id) {return paymentFeignService.getPaymentById(id);}
​/*** 模拟feign超时** @return*/@GetMapping(value = "/payment/feign/timeout")public String paymentFeignTimeout() {// openfeign-ribbon, 客户端一般默认等待1秒钟return paymentFeignService.paymentFeignTimeout();}
}

8、测试出现访问超时错误,原因是,feign客户端默认超时时间是1秒,超时就出现异常。

解决办法:yml配置中添加

# 设置feign客户端超时时间(OpenFeign默认支持ribbon)
ribbon:# 指的是建立连接所用的时间,适用于网络状态正常的情况下,两端连接所用的时间ReadTimeout: 5000# 指的是建立连接后从服务器读取到可用资源所用的时间ConnectTimeout: 5000

9、访问成功!

OpenFeign日志增强

openfeign提供了日志打印功能。

Logger有四种类型:NONE(默认)BASICHEADERSFULL,通过注册Bean来设置日志记录级别

1、配置类

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

2、yml配置文件添加

logging:level:# feign日志以什么级别监控哪个接口com.atguigu.springcloud.service.PaymentFeignService: debug

十三、Hystrix

官网:https://github.com/Netflix/Hystrix/wiki

参考博客:https://www.cnblogs.com/cjsblog/p/9391819.html

篇幅臃肿,分为两部分,详情见:周阳springcloud第二部分笔记

springCloud2020尚硅谷周阳老师教程相关推荐

  1. 【学习笔记】尚硅谷周阳老师的Docker教程学习笔记

    本文是尚硅谷周阳老师的Docker教程的相关学习笔记,暂时还没有检查勘误过. 一.Docker简介 1. 简介 Docker的出现是为了解决代码在本机可以正常运行,而部署到其他机器不能运行的问题.这是 ...

  2. 尚硅谷周阳老师2020年 SpringCloud(H版和Alibaba) 视频教程学习时整理的笔记记录和代码

    尚硅谷周阳老师2020年 SpringCloud(H版和Alibaba)视频教程学习时整理的笔记记录和代码 尚硅谷周阳老师SpringCloud(H版和Alibaba)学习.代码摘录,下面是各个mod ...

  3. 尚硅谷周阳老师 SpringCloud第二季学习笔记

    前言:首先感谢尚硅谷周阳老师的讲解,让我对springcloud有了很好的理解,周阳老师的讲课风格真的很喜欢,内容充实也很幽默,随口一说就是一个段子,我也算是周阳老师的忠实粉丝啦. 先说说课程总体内容 ...

  4. 尚硅谷周阳老师-redis脑图课件

    因为脑图原件是.mmap格式,使用wps和xmind打开都会有格式不兼容的问题,这里我们可以使用mindmanager试用版存为html5交互式格式, 提供在线阅读.因为阿里云学生服务器带宽有限,这里 ...

  5. 尚硅谷 周阳老师 SpringCloud 学习笔记

    十二.服务配置 Spring Cloud Config 配置中心 1. 概述 1.1 分布式系统面临的配置问题 微服务意味着要将单体应用中的业务拆分成一个个子服务,每个服务的粒度相对较小,因此系统中会 ...

  6. 尚硅谷周阳老师2020最新Springcloud完整版学习

    最新SpringCloud 目录 最新SpringCloud 一.简介 版本号的对应 总架构: 二.入门体验 父工程的创建: cloud-provider-payment-8001 开启热部署: cl ...

  7. JUC笔记之尚硅谷周阳老师思维导图整理

    文章目录 1. JUC 是什么 2. Lock 接口 3. Lambda表达式复习--详情请看 on java 8 4. 线程间通信 5. 线程间定制化调用通信 6.NotSafeDemo 7.多线程 ...

  8. 尚硅谷周阳老师Docker笔记

    链接:https://pan.baidu.com/s/1UZD4vDzGzTK8YM9p37TKew 提取码:gjgj 我的Docker笔记 Docker学习笔记(一)–基础篇 Docker学习笔记( ...

  9. JavaScript(基础、高级)笔记汇总表【尚硅谷JavaScript全套教程完整版】

    目   录 前言 JavaScript(基础+高级)配套资料下载 JavaScript 基础 学习地址 学习笔记 day 05(P001-P006)[2016.11.22] day 06(P007-P ...

最新文章

  1. 深度整合英特尔傲腾,SmartX首发100us级超低延迟超融合解决方案
  2. 【boundfield】GridView中BoundField与TemplateField的区别_boundfield
  3. 汽车之家购买价格PC真正的原因阿拉丁
  4. 图解用户登录验证业务流程(推荐)
  5. .net 4.0新特性-tuple
  6. progressbar 自定义样式_Progressbar的简单使用
  7. c语言对n个数选择排序_选择排序_C语言「抄作业」
  8. 使用词向量嵌入模型,获得近义词
  9. 【CNN】连续学习/持续学习,增量学习
  10. android_基础_修改系统背景(状态栏颜色、导航栏颜色、标题栏颜色等等)
  11. 基于android的二维码会议签到app管理系统
  12. 计算机毕业设计ssm基于Andriod的剪纸艺术平台3swaq系统+程序+源码+lw+远程部署
  13. T007V-采购订单税码对应的税率所在后台表
  14. IPEmotion新增功能:交流电功率分析计算
  15. Harbor项目高手问答及赠书活动火热进行中
  16. R语言安装Sampling包
  17. C++ 利用 windbg + dump + map + cod 文件分析 crash 原因
  18. docker 使用数据库mysql
  19. Qt-qss之QSlider滑动条美化
  20. DDD:聚合根的批量删除是不是可以批量发送请求

热门文章

  1. 【教程】如何查看下载谷歌历史影像
  2. mysql 遍历XML_mybatis 中,mapper.xml中遍历list集合知识点
  3. html网页中的锚点(命名锚记)的使用介绍
  4. 牛津大学计算机科学硕士,牛津大学计算机科学硕士专业
  5. 游戏引擎unity3d_安装Unity 3D游戏引擎
  6. 河海大学计算机与信息学院王敏,计算机技术-河海大学计算机与信息学院.PDF
  7. 数据竞赛:联通套餐个性化匹配
  8. 这可能是最精简的Android6.0运行时权限处理,百行代码的工具类,支持Rationale,附:各种权限详细处理
  9. 数据在计算机中的存储方式
  10. 非常NB的一款快捷启动软件--Merry