分布式缓存 Redis

单点Redis的问题

  • **数据丢失问题:**Redis是内存存储,服务重启可能会丢失数据
  • **并发能力问题:**单节点Redis并发能力虽然不错,但也无法满足如618这样的高并发场景
  • 故障恢复问题:如果Redis宕机,则服务不可用,需要一种自动的故障恢复手段
  • **存储能力问题:**Redis基于内存,单节点能存储的数据量难以满足海量数据需求
  • img

img点击并拖拽以移动

img

img点击并拖拽以移动

解决问题

img

一、Redis持久化

(73条消息) (分布式缓存)Redis持久化_其然乐衣的博客-CSDN博客

二、Redis主从

(73条消息) (分布式缓存)Redis主从_其然乐衣的博客-CSDN博客

三、Redis哨兵

(73条消息) (分布式缓存)Redis哨兵_其然乐衣的博客-CSDN博客

四、Redis分片集群

(74条消息) (分布式缓存)Redis分片集群_其然乐衣的博客-CSDN博客

Sentinel-微服务整合sentinel

1.引入sentinel依赖:

1
2
3
4
5
<!--引入sentinel依赖-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
  1. 配置控制台地址(让微服务与控制台建立联系):

1
2
3
4
5
spring:
cloud:
sentinel:
transport:
dashboard: localhost:8080 # sentinel控制台地址

(如果是在云服务器上安装的sentinel,localhost改为服务器的ip地址)

Sentinel-Feign整合Sentinel

步骤一:在feing-api项目中定义类,实现FallbackFactory:

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import cn.itcast.feign.clients.UserClient;
import cn.itcast.feign.pojo.User;
import feign.hystrix.FallbackFactory;
import lombok.extern.slf4j.Slf4j;

@Slf4j //记录日志
public class UserClientFallbackFactory implements FallbackFactory<UserClient> {

@Override
public UserClient create(Throwable throwable) {
return new UserClient() {
/*
在这个方法中编写降级的业务逻辑
返回 友好提示、一个默认值...,都可以在这个方法里面写
*/
@Override
public User findById(Long id) { //在这个方法中编写降级的逻辑
log.error("查询用户异常",throwable);
return new User();
}
};
}
}

步骤二: 在feing-api项目中的DefaultFeignConfiguration类中将UserClientFallbackFactory注册为一个Bean:

代码:

1
2
3
4
5
//将UserClientFallbackFactory注册到bean
@Bean
public UserClientFallbackFactory userClientFallbackFactory(){
return new UserClientFallbackFactory();
}

步骤三: 在feing-api项目中的UserClient接口中使用UserClientFallbackFactory:

代码:

1
2
3
4
5
6
7
8
9
10
11
12
import cn.itcast.feign.clients.fallback.UserClientFallbackFactory;
import cn.itcast.feign.pojo.User;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;

@FeignClient(value = "userservice", fallbackFactory = UserClientFallbackFactory.class)
public interface UserClient {

@GetMapping("/user/{id}")
User findById(@PathVariable("id") Long id);
}

Java后端数据返回通用类

第一种(简单定义)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
import lombok.Data;
import java.util.HashMap;
import java.util.Map;

/**
* 结果数据返回通用类
* @author : 其然乐衣Letitbe
* @date : 2022/7/20
*/

@Data
public class JsonResult<T> {
/**
* 编码:200成功,500和其它数字为失败或其他异常情况
**/
private Integer code;
/**
* 返回描述信息
**/
private String msg;
/**
* 信息返回数据
**/
private T data;
/**
* 动态数据
**/

private Map map = new HashMap();

public JsonResult() {

}

public JsonResult(Integer code) {
this.code = code;
}

public JsonResult(Integer code, String msg) {
this.code = code;
this.msg = msg;
}

public static <T> JsonResult<T> success(String msg, T object) {
JsonResult<T> result = new JsonResult<T>();
result.msg = msg;
result.data = object;
result.code = 200;
return result;
}

public static <T> JsonResult<T> success(String msg, Integer code) {
JsonResult result = new JsonResult();
result.msg = msg;
result.code = code;
return result;
}

public static <T> JsonResult<T> success( Integer code) {
JsonResult result = new JsonResult();
result.code = code;
return result;
}

public static <T> JsonResult<T> success(String msg) {
JsonResult<T> result = new JsonResult<T>();
result.msg = msg;
result.code = 200;
return result;
}

public static <T> JsonResult<T> success() {
JsonResult<T> result = new JsonResult<T>();
result.code = 200;
return result;
}

public static <T> JsonResult<T> error(String msg, Integer code) {
JsonResult result = new JsonResult();
result.msg = msg;
result.code = code;
return result;
}

public static <T> JsonResult<T> error(Integer code) {
JsonResult result = new JsonResult();
result.code = code;
return result;
}

public static <T> JsonResult<T> error() {
JsonResult result = new JsonResult();
result.code = 500;
return result;
}

public JsonResult<T> add(String key, Object value) {
this.map.put(key, value);
return this;
}
}

第二种

导入依赖

1
2
3
4
5
6
<dependency>
<groupId>io.swagger</groupId>
<artifactId>swagger-annotations</artifactId>
<version>1.5.13</version>
<scope>compile</scope>
</dependency>

编写结果数据通用类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
import com.fasterxml.jackson.annotation.JsonInclude;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;

import java.io.Serializable;

/**
* todo JsonInclude 注解忽略 null值 看业务需求添加
*/
@ApiModel("分页响应参数")
@JsonInclude(value = JsonInclude.Include.NON_EMPTY)
@Data
public class JsonResult<T> implements Serializable {
private static final long serialVersionUID = 1L;

@ApiModelProperty(value = "结果状态", example = "200")
private Integer code = 200;
@ApiModelProperty(value = "结果数据")
private T data;
@ApiModelProperty(value = "结果描述", example = "成功")
private String message = "ok";

public JsonResult() {
}

public JsonResult(Integer code) {
this();
this.code = code;
}

public JsonResult(Integer code, T data) {
this(code);
this.data = data;
}

public JsonResult(Integer code, String message) {
this(code);
this.message = message;
}

public JsonResult(Integer code, String message, T data) {
this(code, message);
this.data = data;
}

public static <T> JsonResult<T> success() {
return new JsonResult(200);
}

public static <T> JsonResult<T> success(T data) {
return new JsonResult(200, data);
}

public static <T> JsonResult<T> success(String message, T data) {
return new JsonResult(200, message, data);
}

public static <T> JsonResult<T> success(Integer code, String message, T data) {
return new JsonResult(code, message, data);
}

public static JsonResult<String> error() {
return new JsonResult(500);
}

public static <T> JsonResult<T> error(String message) {
return new JsonResult(500, message);
}

public static <T> JsonResult<T> error(Integer code, String message) {
return new JsonResult(code, message);
}

public static <T> JsonResult<T> error(Integer code, String message, T data) {
return new JsonResult(code, message, data);
}


}

Sentinel-授权规则及规则持久化

一、授权规则

第1步:添加判断来源逻辑

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package cn.itcast.order.sentinel;

import com.alibaba.csp.sentinel.adapter.spring.webmvc.callback.RequestOriginParser;
import org.apache.commons.lang.StringUtils;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;

@Component
public class HeaderOriginParser implements RequestOriginParser {


@Override
public String parseOrigin(HttpServletRequest request) {

//1.获取请求头
String origin = request.getHeader( "origin" );
//2.非空判断
if ( StringUtils.isEmpty( origin ) ) {
origin = "blank"; //如果是空的就返回一个默认值
}

return origin;
}
}

第2步:编写过滤器–>给网关添加origin头

第3步: 重新启动,访问一个接口

第4步:

第5步:重新范访问上面的接口

8088是直接范文oder服务的,也就是绕过了网关,但是现在我们已经设置了授权规则,所以直接访问是不行的,你报错如下

现在我们通过网关来访问,网关的端口设置的是10010,用10010来访问

因为网关做了权限的校验,所以需要把权限加上

因此,通过上面的授权规则设置后,通过网关来访问的就可以,而通过浏览器直接访问的就不行

下面是黑马PPT的笔记

二、自定义异常结果

第1步:

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
package cn.itcast.order.sentinel;

import com.alibaba.csp.sentinel.adapter.spring.webmvc.callback.BlockExceptionHandler;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.alibaba.csp.sentinel.slots.block.authority.AuthorityException;
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeException;
import com.alibaba.csp.sentinel.slots.block.flow.FlowException;
import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowException;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@Component
public class SentinelExceptionHandler implements BlockExceptionHandler {

@Override
public void handle(HttpServletRequest request, HttpServletResponse response, BlockException e) throws Exception {
String msg = "未知异常";
int status = 429;

if ( e instanceof FlowException ) {
msg = "请求被限流了";
} else if ( e instanceof ParamFlowException ) {
msg = "请求被热点参数限流";
} else if ( e instanceof DegradeException ) {
msg = "请求被降级了";
} else if ( e instanceof AuthorityException ) {
msg = "没有权限访问";
status = 401;
}

response.setContentType( "application/json;charset=utf-8" );
response.setStatus( status );
response.getWriter().println( "{"msg": " + msg +", "status": " + status + "}" );
}
}

第2步: 重启微服务,然后浏览器访问接口

第3步: 在sentinel中给端口设置响应的规则进行测试

(1)新增授权规则

(2)新增流控规则

下面是黑马PPT笔记

三、规则持久化

这一节知识就没有详细的操作笔记,需要观看黑马教程视频如下链接:

高级篇Day1-04-授权规则及规则持久化_哔哩哔哩_bilibili

前言:

当我们服务重启,我们所配的所有规则就会丢失,因为sentinel会默认把这些规则保存在内存里,重启就自然丢失了。而在生产的环境下 肯定是不能容忍这样的问题的。所以我们就需要学习怎么将****sentinel的规则持久化。

Sentinel-认识和(本地)安装sentinel

首先先了解一下雪崩问题:

img

img

img

如何避免因服务故障引起的雪崩问题?

  • 超时处理:

    • 我们会给业务设置超时时间,如果一个业务执行时超过了这个时间还是没有给出响应,我们就立即释放资源,返回异常信息,从而避免无休止的等待导致资源耗尽,避免雪崩
  • 线程隔离:

    • 将每一个服务隔离开,设置单独一个线程池,这样呢,一旦一个业务出现了故障,它也只是把这一个线程池里的资源耗尽,而不会导致整个tomcat资源耗尽
  • 降级熔断:

    • 我们会去统计一个业务的正常请求和异常请求的比例,如果异常请求的比例过高,我们会认为,这个请求或业务会比较危险,我们就可以阻止这个业务的请求,快速失败,释放资源,从而避免雪崩问题发生。在这个业务恢复正常以后,我才会放心访问这个业务的请求

服务保护技术对比

img

Sentinel是阿里巴巴开源的一款微服务流量控制组件。官网地址:https://sentinelguard.io/zh-cn/index.html

Sentinel 具有以下特征:

丰富的应用场景:Sentinel 承接了阿里巴巴近 10 年的双十一大促流量的核心场景,例如秒杀(即突发流量控制在系统容量可以承受的范围)、消息削峰填谷、集群流量控制、实时熔断下游不可用应用等。

完备的实时监控:Sentinel 同时提供实时的监控功能。您可以在控制台中看到接入应用的单台机器秒级数据,甚至 500 台以下规模的集群的汇总运行情况。

广泛的开源生态:Sentinel 提供开箱即用的与其它开源框架/库的整合模块,例如与 Spring Cloud、Dubbo、gRPC 的整合。您只需要引入相应的依赖并进行简单的配置即可快速地接入 Sentinel。

完善的 SPI 扩展点:Sentinel 提供简单易用、完善的 SPI 扩展接口。您可以通过实现扩展接口来快速地定制逻辑。例如定制规则管理、适配动态数据源等。

Sentinel-流控效果

流控效果是指请求达到流控阈值时应该采取的措施,包括三种:

• 快速失败:达到阈值后,新的请求会被立即拒绝并抛出FlowException异常。是默认的处理方式。

• warm up:预热模式,对超出阈值的请求同样是拒绝并抛出异常。但这种模式阈值会动态变化,从一个较小值逐渐增加到最大阈值。

• 排队等待:让所有的请求按照先后次序排队执行,两个请求的间隔不能小于指定时长

流控效果-warm up

warm up也叫预热模式,是应对服务冷启动的一种方案。请求阈值初始值是 threshold / coldFactor,持续指定时长后,逐渐提高到threshold值。而coldFactor的默认值是3.

添加规则:

借助JMeter测试:

启动后,通过的会随着预热过程而逐渐变多,拒绝逐渐变少

实时监控显示:

流控效果-排队等待

排队等待的有点:

   (1) 发出的QPS是15,但大多数是通过的(10)而其它有的都是放到队列里面去了,小部分是拒绝的。

   (2)而且不管你发出QPS的波动有多么剧烈,我放出的永远是均衡的,同过的都是10。这样对于微服务来说,波动的肯定不比均衡的好,所以这样的作用也是起到了流量整形的作用

RestTemplate远程调用

步骤 1:注册RestTemplate

在order-service的OrderApplication中注册RestTemplate

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@SpringBootApplication
public class SquareApplication {
public static void main(String[] args) {
SpringApplication.run(SquareApplication.class,args);
}

/**
* 创建RestTemplate 并注入Spring容器
* @return
*/
@Bean
public RestTemplate restTemplate(){
return new RestTemplate();
}
}