- Day01~微服务架构入门
- 核心知识点
- 常见问题分析
- 常见Bug分析
- 课后作业
- 作业答案
- Day02~Nacos 注册中心入门
- 核心知识点
- 常见问题分析
- 常见Bug分析
- 课后作业
- Day03~服务发现及调用
- 核心知识点
- 常见问题分析
- 常见Bug分析
- 课后作业
- 作业答案
- Day04~Nacos 配置中心入门
- 核心知识点
- 常见问题分析
- 常见Bug分析
- 课后作业
- Day05~Nacos配置管理模型
- 核心知识点
- 常见问题分析
- 常见Bug分析
- 课后作业
- 作业答案
- Day06~Sentinel应用入门
- 核心知识点
- 常见问题分析
- 常见Bug分析
- 课后作业
- 作业答案
- Day07~Sentinel应用进阶
- 核心知识点
- 常见问题分析
- 常见Bug分析
- 课后作业
- 作业答案
- 微服务架构诞生的背景(分而治之~北京一个火车站到多个火车站)
- 微服务架构解决方案(大厂自研,Spring Cloud ~Netflix,Alibaba,…)
- 微服务架构下Maven项目的创建方式?(Maven聚合项目~资源复用,简化编译,打包,部署方式)
- 微服务架构入门聚合项目创建?(01-sca,sca-consumer,sca-provider,sca-gateway,sca-common)
- 为什么需要微服务?(对系统分而治,解决因并发访问过大带来的系统复杂性(例如:业务,开发,测试,升级,可靠性等)
- 微服务设计的特点?(单一职责,独立进程,开发测试效率高,可靠性高,升级难度小,但会带来一定的维护成本)
- 微服务解决方案有哪些?(大厂自研,中小企业采用开源Spring Cloud Alibaba,Spring Cloud Netfix等 )
- 微服务设计中需要哪些关键组件(服务的注册,发现,配置,限流降级,访问入口管理,分布式事务管理等)
- 创建聚合工程的目的?(实现工程之间资源的的共享)
- 如何修改聚合工程中项目的编译和运行版本?(build->plugins->plugin->maven-compiler-plugin)
- maven工程中build元素的作用?(定义项目的编译,打包方式)
- maven父工程的packaging元素内的值是什么?(pom)
- maven父工程中dependencyManagement元素的作用是什么?(定义项目的版本)
- 依赖或插件无法下载或加载?(本地库冲突,网络不好,maven镜像配置,指定版本在远程服务器不存在,清idea缓存后重启)
- 项目的pom.xml文件有删除线?(idea/setting/build,Execution,Deployment/build Tools/maven/ignore Files)
- 基于原有工程结构,在01-sca工程下创建sca-common工程.
- 在sca-provider工程中引入sca-common工程依赖.
- 在sca-provider工程中使用sca-common工程中的一些公共类(例如StringUtils.java)
- 检查自己电脑环境是否配置了JAVA_HOME环境变量?(set JAVA_HOME)
- 检查自己电脑环境中的JVM虚拟机是否为64位的?(java -version)
- 检查自己电脑环境中mysql的版本是否为5.7以上?(假如是MariaDB ,建议10.5以上)
- 预习Nacos服务注册中心相关内容.
- sca-common 工程创建,例如:
- sca-provider 工程引入sca-common工程依赖
com.jt sca-common 1.0-SNAPSHOT
- sca-provider工程中使用sca-common工程公共类StringUtils.java
第一步:在sca-common工程中创建StringUtil.java工具类,例如:
package com.jt.common.util;
public class StringUtils {
public static boolean isEmpty(String str){
return str==null||"".equals(str);
}
}
第二步:在sca-provider工程中添加spring-boot-start依赖,例如:
org.springframework.boot spring-boot-starter
第三步:在sca-provider工程中的test目录下定义单元测试类,并进行测试,例如:
package com.example;
import com.jt.common.util.StringUtils;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootConfiguration //(假如工程中有启动类,注解就不需要加了)
@SpringBootTest
public class StringTests {
@Test
void testStringEmpty(){
String content="helloworld";
boolean flag= StringUtils.isEmpty(content);
System.out.println(flag);
}
}
-
检查自己电脑的JAVA_HOME环境变量?
方式1:打开电脑的命令行窗口,输入set JAVA_HOME 指令,例如:
方式2:假如是window平台还可以通过,如下方式进行检查,例如:
-
检查自己电脑环境中的JVM虚拟机是否为64位的,例如:
-
登录mysq,检查mysql或maridb的版本,例如:
- 注册中心诞生的背景? (所有公司需要在工商局进行备案)
- 注册中心的选型?(社区活跃度,稳定性,功能,性能,学习成本)
- Nacos下载,安装,配置,启动,访问(http://ip:port/nacos)
- 服务向Nacos的注册?(添加依赖,服务配置,启动服务并检查)
- Nacos进行服务监控的方式?(心跳包)
- 服务(进程)之间的调用?(借助RestTemplate.思考Spring中的JdbcTemplate,SqlSessionTemplate等对象的作用)
- 注册中心诞生的背景? (服务多了,需要对服务进行更好管理)
- 市场上常用的注册中心?(Zookeeper,Eureka,Nacos,Consul)
- 如何对注册中心进行选型?(社区活跃度,稳定性,功能,性能,学习成本)
- Nacos 是什么?(是Alibaba公司基于SpringBoo技术t实现的一个注册中心,本质上也是一个服务)
- Nacos 的基本架构?(Client/Server架构)
- Nacos 主要提供了什么核心功能?(服务的注册,发现,配置)
- Nacos 服务启动需要什么前置条件?(配置JDK的JAVA_HOME目录,安装MySQL5.7以上版本,配置连接的数据库)
- Nacos 服务单机模式,window平台下启动时的指令是什么?(startup.cmd -m standalone)
- 实现Nacos服务注册需要添加什么依赖?(两个)
- 实现Nacos服务注册时,必须做哪些配置?(服务名,假如是本机服务注册可以省略服务地址)
- Nacos如何检查服务状态?(通过心跳包实现)
- 服务之间进行服务调用时,使用了什么API?(RestTemplate)
-
JAVA_HOME环境变量定义错误,例如:
说明,这里一定要注意JAVA_HOME单词的拼写,JAVA_HOME中定义的JDK是存在的. -
MySQL版本比较低,例如:
当执行nacos-mysql.sql文件时,出现如下错误:
-
SQL文件应用错误,例如:
-
Nacos的application.properties配置文件中,连接数据库的配置错误.
-
服务注册时,服务名不正确,例如:
-
Nacos 服务注册失败,例如
-
客户端500异常,例如
-
服务调用时,连接异常,例如:
-
服务调用时底层404问题,例如:
- 重新创建新的聚合项目(例如10-sca)
- 重新实现服务的注册和调用(ca-consumer调用ca-provider)
- 尝试完成负载均衡方式的服务调用(参考博客文档)
- 基于LoadBalancerClient 实现服务发现和负载均衡
- @Loadbalanced注解的作用以及应用分析
- Feign方式的服务调用实践
- 何为服务发现?(从nacos获取服务实例)
- LoadBalancerClient的作用?(从nacos获取服务实例列表,然后本地基于负载均衡算法获取服务实例)
- @Loadbalanced作用?(描述RestTemplate对象,让系统底层为RestTemplate对象赋能)
- Feign是什么?(Spring Cloud微服务规范中的一组远程调用API)
- 为什么使用Feign?(优化服务调用结构)
- 如何使用Feign实现服务调用?(依赖,@EnableFeignClients,@FeignClient)
- Feign方式的服务调用原理是怎样的?(底层基于代理对象实现)
- 服务启动时端口被占用,例如:
- RestTemplate对象应用错误,例如:
- @Autowired描述的属性有红色下划波浪线,例如:
- 依赖注入(DI)问题,例如
- 服务名应用问题,例如:
- Feign 接口方法中@PathVariable注解应用问题.
- 总结微服务中的服务发现及调用方式.
- 构建一个简易的Browser/Tomcat对象,实现两个对象的网络通讯(了解).
- 预习@FeignClient中fallbackFactory注解的应用.
- 预习Nacos配置中心.
- 服务调用方式总结,例如:
- 简易Browser/Tomcat对象实现?(Java中的网络通讯需要借助Socket/ServerSocket对象)
第一步:构建Tomcat对象,例如:
package com.jt.common.net;
import java.io.IOException;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
public class Tomcat {//企业规范
public static void main(String[] args) throws IOException {
//1.创建服务(例如:启动nacos,启动....),并在9999端口进行监听
//网络中计算机的唯一标识是什么?ip
//计算机中应用程序的唯一标识是什么?port
ServerSocket server=new ServerSocket(9999);
System.out.println("server start ...");
//2.启动服务监听
while(true){
//监听客户端的链接(这里的socket代码客户端对象)
Socket socket=server.accept();//阻塞方法
//在这里可以将socket对象的信息记录一下.(服务注册)
//创建输出流对象,向客户端输出hello client
OutputStream out =
socket.getOutputStream();
//byte[] responseContent="hello client".getBytes();
byte[] responseContent=("HTTP/1.1 200 ok rn" +
"Content-Type: text/html;charset=utf-8 rn" +
"rn" +
"hello client").getBytes();
out.write(responseContent);
out.flush();
}
}
}
第二步:构建Browser对象,例如:
package com.jt.common.net;
import java.io.IOException;
import java.io.InputStream;
import java.net.Socket;
public class Browser {
public static void main(String[] args) throws IOException {
//1.创建网络编程中的客户端对象(Socket)
//构建Socket对象时要执行连接个计算机(ip),访问计算机中的哪个应用(port)
Socket socket=new Socket("127.0.0.1",9999);//TCP
//2.创建一个输入流对象,读取服务端写到客户端的数据
InputStream in = socket.getInputStream();
byte[] buf=new byte[1024];
int len=in.read(buf);
String content=new String(buf,0,len);
System.out.println(content);
//3.释放资源
socket.close();
}
}
第三步:运行tomcat和Browser,检测Browser输出.
也可以打开,浏览器直接访问http://localhost:9999
- 配置中心诞生的背景(分布式架构下如何动态发布和更新配置)
- 市场上主流的配置中心(Nacos,…)
- Nacos配置中心的基本应用(添加依赖,修改配置文件名以及内容,新建配置,应用配置)
*@RefreshScope注解的应用(描述类,实现类中属性值@Value与配置中心中内容的同步)
- 为什么要使用配置中心?(项目上线后实现配置的动态发布和更新)
- 你知道哪些主流配置中心?
- Nacos配置中心如何应用?
- Java项目中你用的日志API是什么?(org.slf4j.Logger)
- 你项目中为什么使用org.slf4j.Logger对象记录日志?此对象是日志规范,是对外面的门面,可以更好降低耦合
- @Slf4j注解的作用是什么?(此注解属于lombok,用于描述类,在类中创建org.slf4j.Logger对象)
- 你知道Java中有哪些常用日志级别?(debug
- Java日志在设计时为什么要设计日志级别?(方便根据级别调整日志输出)
- Java中日志级别的配置通常要写到哪里?(配置中心)
- 你觉得Nacos配置中心通常会配置哪些内容?(可能经常变化的数据)
- Nacos配置中心依赖对项目中配置文件的名字有要求吗?(bootstrap.yml)
- Nacos中@RefreshScope注解的应用场景以及作用.
- 如何在初始化对象属性时从配置中心读取配置内容?(@Value注解对属性进行描述)
- idea连接database,例如mysql.(可以baidu,然后输入idea database进行复合查询)
-
配置文件或配置文件格式不正确,例如:
-
@Value注解包引入错误,例如:
*@Value配置读取不到,例如:
- 总结课上知识点,并尝试重新构建项目进行配置实践.
- 将Nacos配置入门章节的小节节面试仔细读一遍.
- 预习Nacos的配置模型.
- 配置管理模型存在的意义
- 配置管理模型三要素及其要素关系
- Nacos中读取指定命名空间和分组下的配置
- Naocs中的共享配置的设计(对很多配置文件中的共性的一种提取方式)
- 如何理解配置的命名空间(Namesapce)?(定义一些配置的作用域,做配置的分类管理,不同环境不同配置)
- 为什么要进行配置分组?(一个命名空间可以有多个分组,同一个生产环境下的不同活动会有不同配置)
- 配置中心运行有共享配置吗,如何引用?(可以,基于shared-configs元素进行引用)
- Java程序中读取不到配置?(依赖,配置文件,命名空间,分组,data-id,空格,缩进,重启,放大招)
- @Value("${logging.level.com.jt}") 读取不到值?(先检查你是否有这个配置,假如没有可以给定一个默认值)
- 线程(Thread)安全问题?(多个线程,共享数据集,原子操作)
- 如何理解线程并发中的双重校验?(既要保证安全,又要保证效率)
- 如何理解定时任务?(让线程对象在规定的时间范围内自动去执行任务,例如配置信息的定时刷新)
- 总结课上内容(命名空间,分组,共享配置)
- 基于Timer对象实现定时任务调度(单线程有顺序的任务调度)
- 基于ScheduledExecutorService实现一个任务调度(课后拓展,Nacos中的定时心跳,配置长轮询都是基于这个对象)
- 在ProviderCacheController类中添加一个本地Cache(基于CopyOnWriteArrayList对象实现)
- 基于双重校验机制,实现类的单实例设计(拓展,自己尝试实现).
- 预习Sentinel章节的限流操作(北京市为什么要限号)
- 基于Timer对象实现定时任务调度,例如:
package com.jt.common.util;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.ScheduledExecutorService;
public class TimerUtils {
public static void main(String[] args) {
//1.构建执行任务的对象(这里选择java中的Timer对象)
//Timer对象创建时会创建一个线程,并且为线程分配一个任务队列
Timer timer=new Timer();
//2.构建任务对象
TimerTask task1=new TimerTask() {
@Override
public void run() {
System.out.println(System.currentTimeMillis());
//timer.cancel(); 结束任务调度
}
};
//3.定时执行任务
timer.schedule(task1,//要执行的任务
1000,//1秒以后开始执行
1000);//每隔1秒执行1次
//基于Timer类执行定时任务时,最大的缺陷是多个任务不能并发执行.
}
}
- 基于ScheduledExecutorService实现一个任务调度,例如:
package com.jt.common.util;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class ScheduledExecutorServiceTests {
public static void main(String[] args) {
//构建一个负责任务调度的线程池对象,池中最多有三个核心线程
ScheduledExecutorService ses=
Executors.newScheduledThreadPool(3);
//构建任务对象
Runnable task=new Runnable() {
@Override
public void run() {
String tName=Thread.currentThread().getName();
System.out.println(tName+"->"+System.currentTimeMillis());
}
};
//执行任务对象(定时任务调度):1秒以后开始执行,每隔1秒执行1次
ses.scheduleAtFixedRate(task,
1,//初始延迟
5,//每隔1秒启动1次(与任务是否执行结束无关)
TimeUnit.SECONDS);//时间单位
}
}
- 在ProviderCacheController类中添加一个本地Cache对象,在保证其线程安全的前提下,提高其性能.
//构建一个本地(Local)缓存对象(基于jvm中的一个对象存储从数据库获取的数据).
private List cache=new CopyOnWriteArrayList<>();
@RequestMapping("/provider/cache02")
public List doUseLocalCache02(){
if(!useLocalCache){//假如没有打开本地Cache,则直接访问数据库
System.out.println("==Get data from database==");
return Arrays.asList("Category-A", "Category-B", "Category-C");
}
if(cache.isEmpty()) {
synchronized (cache) {
if (cache.isEmpty()) {//Thread-A,Thread-B,...
System.out.println("==Get data from database==");
//假设这部分分类信息是从数据库获取的,但是,我不希望每次获取分类信息都要从数据库查
List cates = Arrays.asList("Category-A", "Category-B", "Category-C");
cache.addAll(cates);
}
}
}
return cache;
}//生产层面
- 基于双重校验机制,实现类的单实例设计,例如:
package com.jt.common.thread;
class Singleton{
private Singleton(){}
private static Singleton instance;
public static Singleton getInstance(){
if(instance==null) {
synchronized (Singleton.class) {
if (instance == null) {//双重校验
instance = new Singleton();
}
}
}
return instance;
}
}
public class SingleTests {
public static void main(String[] args) {
Thread t1=new Thread(){
@Override
public void run() {
System.out.println(Singleton.getInstance());
}
};
Thread t2=new Thread(){
@Override
public void run() {
System.out.println(Singleton.getInstance());
}
};
Thread t3=new Thread(){
@Override
public void run() {
System.out.println(Singleton.getInstance());
}
};
t1.start();
t2.start();
t3.start();
}
}
Day06~Sentinel应用入门
核心知识点
- 限流的背景
- Sentinel限流入门(控制台8180-定义规则,客户端服务应用规则:依赖,配置)
- Sentinel常用限流模式(直接,关联,链路)
- Sentinel降级入门实现(暂停对服务的访问)
- 为什么要进行限流? (系统处理能力有限,可以通过限流方式,保证系统可靠运行)
- Sentinel限流的基本原理?(底层对请求进行拦截,然后通过流控规则限定对资源访问)
- Sentinel限流有哪些算法? (计数器,令牌桶,漏桶,滑动窗口算法~sentinel默认)
- Sentinel常用限流效果有哪些?(快速失败,预热,排队)
- sentinel 服务启动不起来?(要使用JDK8)
- sentinel 面板不显示我们的服务?(依赖,配置,访问,大招)
- 总结课上知识点.
- 了解常用限流算法.
- 尝试基于@SentinelResource注解描述的方法进行限流结果的定义?
- 预习sentinel降级异常处理,Sentinel热点规则设计,授权设计.
- 基于@SentinelResource注解描述的方法进行限流结果的定义.
第一步:定义blockHandlerClass,例如:
package com.jt.provider.service;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
@Slf4j
@Component
public class ResourceBlockHandler {
public static String call(BlockException ex){
log.error("block exception {}", ex.getMessage());
return "访问太频繁了,稍等片刻再访问";
}
}
第二步:修改@SentinelResource注解中的属性定义,例如:
@SentinelResource(value="doGetResource",
blockHandlerClass = ResourceBlockHandler.class,
blockHandler = "call")
public String doGetResource(){
return "do get resource";
}
第三步:在controller方法中,调用@Sentinel注解描述的方法,例如:
@GetMapping("/sentinel03")
public String doSentinel03(){
return resourceService.doGetResource();
//return "sentinel 03 test";
}
Day07~Sentinel应用进阶
核心知识点
- Sentinel 异常处理
- Sentinel 热点数据限流
- Sentinel 系统规则应用实践
- Sentinel 授权规则应用实践
- Sentinel 拦截器应用原理分析
- 为什么要进行异常处理?(提高用户体验)
- Sentinel中限流,降级的父类异常类型是什么?(BlockException)
- Sentinel中默认的BlockException处理对象是谁?(DefaultBlockException)
- 如何自己定义Sentinel的异常处理对象?(直接或间接继承BlockExceptionHandler)
- 如何理解热点数据以及限流的方式是什么?(经常访问的数据,基于参数或参数值进行限流)
- 如何理解sentinel中的系统规则?(全局限流规则,基于QPS,CPU,…)
- 如何理解Sentinel中的授权规则?(黑白名单)
- 请求被限流时出现500异常?
- 异常处理对象没有交给Spring管理(例如类使用@Common修饰)
- 总结课上sentinel应用规则
- 实践Spring MVC中的拦截器应用(重点)
- 手写一个简易的框架执行链(了解)
- 预习微服务中的Gateway组件.
- 基于Spring MVC中的拦截器实现对控制方法进行时间访问控制?
package com.jt.provider.interceptor;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.time.LocalTime;
public class TimeInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
System.out.println("===preHandle===");
//业务:允许在8点之后,晚9点之前可访问
LocalTime now = LocalTime.now();//获取当前时间,
int hour = now.getHour();//获取当前时间的小时单位
if(hour<8||hour>=21)
throw new RuntimeException("请在规定时间访问8~21");
return true;
}
}
第二步:配置拦截器,例如:
package com.jt;
import com.jt.provider.interceptor.TimeInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class SpringWebConfig implements WebMvcConfigurer {
//注册拦截器
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new TimeInterceptor())
.addPathPatterns("/provider/sentinel01");
}
}
第三步:对/provider/sentinel01路径按时间段进行访问,检测拦截器的执行.
- 手写一个简易的框架执行链(了解),模拟框架中执行链的设计,例如:
package com.jt.common.interceptor;
import javax.xml.transform.Source;
import java.time.LocalTime;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
//拦截器接口
interface HandlerInterceptor{
default void before(){}//定义在目标handler方法执行之前执行
default void after(){}//定义在目标handler方法之后执行
}
//处理器接口
interface Handler{
void processed();//处理业务的方法
}
//定义一个执行链
class ExecutionChain{//我是执行链的设计者
//一些拦截器
private List interceptors=
new CopyOnWriteArrayList<>();
//业务处理器
private Handler handler;
public ExecutionChain(List interceptors,Handler handler){
this.handler=handler;
this.interceptors.addAll(interceptors);
}
public void execute(){//负责执行业务的方法(例如处理请求)
//1.before
for(int i=0;i=0;i--){
interceptors.get(i).after();
}
}
}
public class frameworkTests {//框架应用者
public static void main(String[] args) {
//应用执行链
//1.创建拦截器
List interceptors=new CopyOnWriteArrayList<>();
HandlerInterceptor interceptor=new HandlerInterceptor() {
@Override
public void before() {
System.out.println("记录考试开始时间:"+ LocalTime.now());
}
@Override
public void after() {
System.out.println("记录考试结束时间:"+ LocalTime.now());
}
};
interceptors.add(interceptor);
//2.创建处理
Handler handler=new Handler() {
@Override
public void processed() {
System.out.println("同学们开始考试");
}
};
//3.创建执行链
ExecutionChain chain=
new ExecutionChain(interceptors,handler);
//4.执行执行链
chain.execute();
}
}



