# spring-cloud-study
**Repository Path**: you-yuan/spring-cloud-study
## Basic Information
- **Project Name**: spring-cloud-study
- **Description**: SpringCloud微服务体系架构。注册中心、服务负载均衡、服务保护、网关、链路追踪、分布式配置中心、消息队列、分布式缓存、系统负载均衡、代码质量监控、日志平台、容器化、自动集成/部署等。
- **Primary Language**: Unknown
- **License**: Not specified
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 4
- **Forks**: 1
- **Created**: 2020-03-31
- **Last Updated**: 2024-10-11
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
# 1. 微服务体系架构
- [1. 微服务体系架构](#1-%E5%BE%AE%E6%9C%8D%E5%8A%A1%E4%BD%93%E7%B3%BB%E6%9E%B6%E6%9E%84)
- [2. 注册中心](#2-%E6%B3%A8%E5%86%8C%E4%B8%AD%E5%BF%83)
- [2.1. 注册中心的主要作用](#21-%E6%B3%A8%E5%86%8C%E4%B8%AD%E5%BF%83%E7%9A%84%E4%B8%BB%E8%A6%81%E4%BD%9C%E7%94%A8)
- [2.2. Eureka](#22-eureka)
- [2.3. Zookeeper](#23-zookeeper)
- [2.4. Consul](#24-consul)
- [2.5. Nacos](#25-nacos)
- [3. 远程服务调用](#3-%E8%BF%9C%E7%A8%8B%E6%9C%8D%E5%8A%A1%E8%B0%83%E7%94%A8)
- [3.1. RestTemplate](#31-resttemplate)
- [3.2. Feign远程服务调用](#32-feign%E8%BF%9C%E7%A8%8B%E6%9C%8D%E5%8A%A1%E8%B0%83%E7%94%A8)
- [4. 服务发现与负载均衡](#4-%E6%9C%8D%E5%8A%A1%E5%8F%91%E7%8E%B0%E4%B8%8E%E8%B4%9F%E8%BD%BD%E5%9D%87%E8%A1%A1)
- [4.1. Ribbon](#41-ribbon)
- [4.2. Feign](#42-feign)
- [4.2.1. 简介](#421-%E7%AE%80%E4%BB%8B)
- [5. 使用多数据源](#5-%E4%BD%BF%E7%94%A8%E5%A4%9A%E6%95%B0%E6%8D%AE%E6%BA%90)
- [6. 数据库垂直拆分与水平拆分](#6-%E6%95%B0%E6%8D%AE%E5%BA%93%E5%9E%82%E7%9B%B4%E6%8B%86%E5%88%86%E4%B8%8E%E6%B0%B4%E5%B9%B3%E6%8B%86%E5%88%86)
- [7. 服务保护(熔断、限流、降级)](#7-%E6%9C%8D%E5%8A%A1%E4%BF%9D%E6%8A%A4%E7%86%94%E6%96%AD%E9%99%90%E6%B5%81%E9%99%8D%E7%BA%A7)
- [7.1. 服务容错的核心概念](#71-%E6%9C%8D%E5%8A%A1%E5%AE%B9%E9%94%99%E7%9A%84%E6%A0%B8%E5%BF%83%E6%A6%82%E5%BF%B5)
- [7.2. Hystrix](#72-hystrix)
- [7.2.1. 断路器聚合监控平台Turbine](#721-%E6%96%AD%E8%B7%AF%E5%99%A8%E8%81%9A%E5%90%88%E7%9B%91%E6%8E%A7%E5%B9%B3%E5%8F%B0turbine)
- [7.3. Sentinel](#73-sentinel)
- [8. API网关](#8-api%E7%BD%91%E5%85%B3)
- [8.1. Zuul](#81-zuul)
- [8.1.1. Zuul网关限流](#811-zuul%E7%BD%91%E5%85%B3%E9%99%90%E6%B5%81)
- [8.2. Spring Cloud Gateway](#82-spring-cloud-gateway)
- [8.3. 网关的负载均衡](#83-%E7%BD%91%E5%85%B3%E7%9A%84%E8%B4%9F%E8%BD%BD%E5%9D%87%E8%A1%A1)
- [9. Docker虚拟化容器](#9-docker%E8%99%9A%E6%8B%9F%E5%8C%96%E5%AE%B9%E5%99%A8)
- [9.1. Docker命令](#91-docker%E5%91%BD%E4%BB%A4)
- [9.1.1. 容器的使用](#911-%E5%AE%B9%E5%99%A8%E7%9A%84%E4%BD%BF%E7%94%A8)
- [9.1.2. 容器的管理](#912-%E5%AE%B9%E5%99%A8%E7%9A%84%E7%AE%A1%E7%90%86)
- [9.1.3. 使用Dockerfile定制镜像](#913-%E4%BD%BF%E7%94%A8dockerfile%E5%AE%9A%E5%88%B6%E9%95%9C%E5%83%8F)
- [10. 服务链路追踪](#10-%E6%9C%8D%E5%8A%A1%E9%93%BE%E8%B7%AF%E8%BF%BD%E8%B8%AA)
- [10.1. Zipkin](#101-zipkin)
- [10.1.1. 使用Docker安装Zipkin服务端](#1011-%E4%BD%BF%E7%94%A8docker%E5%AE%89%E8%A3%85zipkin%E6%9C%8D%E5%8A%A1%E7%AB%AF)
- [10.1.2. 使用Zipkin的Jar包直接部署](#1012-%E4%BD%BF%E7%94%A8zipkin%E7%9A%84jar%E5%8C%85%E7%9B%B4%E6%8E%A5%E9%83%A8%E7%BD%B2)
- [11. 消息中间件](#11-%E6%B6%88%E6%81%AF%E4%B8%AD%E9%97%B4%E4%BB%B6)
- [11.1. RabbitMQ](#111-rabbitmq)
- [12. 分布式缓存Redis](#12-%E5%88%86%E5%B8%83%E5%BC%8F%E7%BC%93%E5%AD%98redis)
- [12.1. Redis安装](#121-redis%E5%AE%89%E8%A3%85)
- [12.2. Redis可视化管理化管理](#122-redis%E5%8F%AF%E8%A7%86%E5%8C%96%E7%AE%A1%E7%90%86%E5%8C%96%E7%AE%A1%E7%90%86)
- [12.3. Redis命令](#123-redis%E5%91%BD%E4%BB%A4)
- [12.4. Redis列表](#124-redis%E5%88%97%E8%A1%A8)
- [12.5. Redis哈希](#125-redis%E5%93%88%E5%B8%8C)
- [12.6. Redis集合](#126-redis%E9%9B%86%E5%90%88)
- [12.7. Redis有序集合](#127-redis%E6%9C%89%E5%BA%8F%E9%9B%86%E5%90%88)
- [12.8. HyperLogLog](#128-hyperloglog)
- [12.9. Redis 事务](#129-redis-%E4%BA%8B%E5%8A%A1)
- [12.10. Redis 脚本](#1210-redis-%E8%84%9A%E6%9C%AC)
- [12.11. Java使用Redis](#1211-java%E4%BD%BF%E7%94%A8redis)
- [12.11.1. Redis的Key命名规范](#12111-redis%E7%9A%84key%E5%91%BD%E5%90%8D%E8%A7%84%E8%8C%83)
- [12.11.2. Redisson项目介绍](#12112-redisson%E9%A1%B9%E7%9B%AE%E4%BB%8B%E7%BB%8D)
- [13. 分布式配置中心](#13-%E5%88%86%E5%B8%83%E5%BC%8F%E9%85%8D%E7%BD%AE%E4%B8%AD%E5%BF%83)
- [13.1. Spring Cloud Config](#131-spring-cloud-config)
- [13.2. Apollo](#132-apollo)
- [13.2.1. 简介](#1321-%E7%AE%80%E4%BB%8B)
- [13.2.2. 部署策略](#1322-%E9%83%A8%E7%BD%B2%E7%AD%96%E7%95%A5)
- [14. 消息总线](#14-%E6%B6%88%E6%81%AF%E6%80%BB%E7%BA%BF)
- [14.1. Spring Cloud Bus](#141-spring-cloud-bus)
- [15. Spring Cloud Stream](#15-spring-cloud-stream)
- [15.1. 概述](#151-%E6%A6%82%E8%BF%B0)
- [15.2. 消息分组](#152-%E6%B6%88%E6%81%AF%E5%88%86%E7%BB%84)
- [16. 系统负载均衡](#16-%E7%B3%BB%E7%BB%9F%E8%B4%9F%E8%BD%BD%E5%9D%87%E8%A1%A1)
- [16.1. Nginx](#161-nginx)
- [16.2. Tengine](#162-tengine)
- [16.3. Haproxy](#163-haproxy)
- [16.3.1. Haproxy简介](#1631-haproxy%E7%AE%80%E4%BB%8B)
- [16.3.2. Haproxy和Nginx的区别](#1632-haproxy%E5%92%8Cnginx%E7%9A%84%E5%8C%BA%E5%88%AB)
- [17. 代码质量管理](#17-%E4%BB%A3%E7%A0%81%E8%B4%A8%E9%87%8F%E7%AE%A1%E7%90%86)
- [17.1. SonarQube](#171-sonarqube)
- [18. 接口负载测试](#18-%E6%8E%A5%E5%8F%A3%E8%B4%9F%E8%BD%BD%E6%B5%8B%E8%AF%95)
- [18.1. JMeter](#181-jmeter)
- [19. 代码设计](#19-%E4%BB%A3%E7%A0%81%E8%AE%BE%E8%AE%A1)
- [19.1. 系统配置加密](#191-%E7%B3%BB%E7%BB%9F%E9%85%8D%E7%BD%AE%E5%8A%A0%E5%AF%86)
- [19.1.1. 使用Jasypt加密配置信息](#1911-%E4%BD%BF%E7%94%A8jasypt%E5%8A%A0%E5%AF%86%E9%85%8D%E7%BD%AE%E4%BF%A1%E6%81%AF)
- [19.1.1.1. 引入Jasypt依赖](#19111-%E5%BC%95%E5%85%A5jasypt%E4%BE%9D%E8%B5%96)
- [19.1.1.2. 加密参数获取密文](#19112-%E5%8A%A0%E5%AF%86%E5%8F%82%E6%95%B0%E8%8E%B7%E5%8F%96%E5%AF%86%E6%96%87)
- [19.1.1.3. 将配置文件改为加密密文](#19113-%E5%B0%86%E9%85%8D%E7%BD%AE%E6%96%87%E4%BB%B6%E6%94%B9%E4%B8%BA%E5%8A%A0%E5%AF%86%E5%AF%86%E6%96%87)
- [19.2. 对象转换性能优化](#192-%E5%AF%B9%E8%B1%A1%E8%BD%AC%E6%8D%A2%E6%80%A7%E8%83%BD%E4%BC%98%E5%8C%96)
- [20. 系统设计](#20-%E7%B3%BB%E7%BB%9F%E8%AE%BE%E8%AE%A1)
- [20.1. 监控SQL服务端执行](#201-%E7%9B%91%E6%8E%A7sql%E6%9C%8D%E5%8A%A1%E7%AB%AF%E6%89%A7%E8%A1%8C)
- [20.1.1. 使用P6Spy监控SQL与慢查询](#2011-%E4%BD%BF%E7%94%A8p6spy%E7%9B%91%E6%8E%A7sql%E4%B8%8E%E6%85%A2%E6%9F%A5%E8%AF%A2)
- [21. 附录](#21-%E9%99%84%E5%BD%95)
- [21.1. Docker部署注意事项](#211-docker%E9%83%A8%E7%BD%B2%E6%B3%A8%E6%84%8F%E4%BA%8B%E9%A1%B9)

# 2. 注册中心
## 2.1. 注册中心的主要作用
服务注册中心是微服务架构非常重要的一个组件,在微服务架构里主要起到了协调者的一个作用。注册中心一般包含如下几个功能:
1. 服务发现:
* 服务注册/反注册:保存服务提供者和服务调用者的信息
* 服务订阅/取消订阅:服务调用者订阅服务提供者的信息,最好有实时推送的功能
* 服务路由(可选):具有筛选整合服务提供者的能力。
2. 服务配置:
* 配置订阅:服务提供者和服务调用者订阅微服务相关的配置
* 配置下发:主动将配置推送给服务提供者和服务调用者
3. 服务健康检测
* 检测服务提供者的健康情况
## 2.2. Eureka
Eureka是在Java语言上,基于Restful Api开发的服务注册与发现组件,Springcloud Netflix中的重要组件。
**Eureka交互流程与原理**
Eureka包含两个组件:Eureka Server 和 Eureka Client,它们的作用如下:
* Eureka Client是一个Java客户端,用于简化与Eureka Server的交互;
* Eureka Server提供服务发现的能力,各个微服务启动时,会通过Eureka Client向Eureka Server
进行注册自己的信息(例如网络信息),Eureka Server会存储该服务的信息;
* 微服务启动后,会周期性地向Eureka Server发送心跳(默认周期为30秒)以续约自己的信息。如果Eureka Server在一定时间内没有接收到某个微服务节点的心跳,Eureka Server将会注销该微服务节点(默认90秒)。
* 每个Eureka Server同时也是Eureka Client,多个Eureka Server之间通过复制的方式完成服务注册表的同步。
* Eureka Client会缓存Eureka Server中的信息。即使所有的Eureka Server节点都宕掉,服务消费者依然可以使用缓存中的信息找到服务提供者。
综上,Eureka通过心跳检测、健康检查和客户端缓存等机制,提高了系统的灵活性、可伸缩性和可用性。
## 2.3. Zookeeper
zookeeper它是一个分布式服务框架,是Apache Hadoop 的一个子项目,它主要是用来解决分布式应用中经常遇到的一些数据管理问题,如:统一命名服务、状态同步服务、集群管理、分布式应用配置项的管理等。简单来说zookeeper=文件系统+监听通知机制。
## 2.4. Consul
Consul是由HashiCorp基于Go语言开发的支持多数据中心分布式高可用的服务发布和注册服务软件,采用Raft算法保证服务的一致性,且支持健康检查。
## 2.5. Nacos
Nacos是一个易于构建云原生应用的动态服务发现、配置管理和服务管理平台。简单来说 Nacos 就是注册中心+配置中心的组合,提供简单易用的特性集,帮助我们解决微服务开发必会涉及到的服务注册与发现,服务配置,服务管理等问题。
**各个注册中心的区别**
| 组件名 | 开发语言 | CAP | 一致性算法 | 服务健康检查 | 对外暴露接口 |
| :-------: | :------: | :--: | :--------: | :----------: | :----------: |
| Eureka | Java | AP | 无 | 可配支持 | HTTP |
| Consul | Go | CP | Raft | 支持 | HTTP/DNS |
| Zookeeper | Java | CP | Paxos | 支持 | 客户端 |
| Nacos | Java | AP | Raft | 支持 | HTTP |
# 3. 远程服务调用
## 3.1. RestTemplate
## 3.2. Feign远程服务调用
# 4. 服务发现与负载均衡
## 4.1. Ribbon
Spring Cloud Ribbon是一个基于HTTP和TCP的客户端负载均衡工具,它基于Netflix Ribbon实现。通过Spring Cloud的封装,可以让我们轻松地将面向服务的REST模版请求自动转换成客户端负载均衡的服务调用。
## 4.2. Feign
### 4.2.1. 简介
Feign是基于Ribbon的再封装,提供了远程服务接口形式的调用功能,并且具备Ribbon的负载均衡等功能。
# 5. 使用多数据源
TODO
# 6. 数据库垂直拆分与水平拆分
TODO
# 7. 服务保护(熔断、限流、降级)
## 7.1. 服务容错的核心概念
**服务雪崩**
在微服务架构中,一个请求需要调用多个服务是非常常见的。如客户端访问A服务,而A服务需要调用B服务,B服务需要调用C服务,由于网络原因或者自身的原因,如果B服务或者C服务不能及时响应,A服务将处于阻塞状态,直到B服务C服务响应。此时若有大量的请求涌入,容器的线程资源会被消耗完毕,导致服务瘫痪。服务与服务之间的依赖性,故障会传播,造成连锁反应,会对整个微服务系统造成灾难性的严重后果,这就是服务故障的“雪崩”效应。
**服务隔离**
服务隔离是指将系统按照一定的原则划分为若干个服务模块,各个模块之间相对独立,无强依赖。当有故障发生时,能将问题和影响隔离在某个模块内部,而不扩散风险,不波及其它模块,不影响整体的系统服务。
**熔断降级**
在互联网系统中,当下游服务因访问压力过大而响应变慢或失败,上游服务为了保护系统整体的可用性,可以暂时切断对下游服务的调用。这种牺牲局部,保全整体的措施就叫做熔断。
所谓降级,就是当某个服务熔断之后,服务器将不再被调用,此时客户端可以自己准备一个本地的fallback策略,返回一个缺省值。

**服务限流**
限流可以认为服务降级的一种,限流就是限制系统的输入和输出流量已达到保护系统的目的。一般来说系统的吞吐量是可以被测算的,为了保证系统的稳固运行,一旦达到的需要限制的阈值,就需要限制流量并采取措施以完成限制流量的目的。比方:推迟解决,拒绝解决,或者部分拒绝解决等等。
## 7.2. Hystrix
Hystrix 是由Netflix开源的一个延迟和容错库,用于隔离访问远程系统、服务或者第三方库,防止级联失败,从而提升系统的可用性与容错性。Hystrix主要通过以下几点实现延迟和容错。
* 包裹请求:使用 HystrixCommand包裹对依赖的调用逻辑,每个命令在独立线程中执行。这使用了设计模式中的“命令模式”。
* 跳闸机制:当某服务的错误率超过一定的阈值时, Hystrix可以自动或手动跳闸,停止请求该服务一段时间。
* 资源隔离: Hystrix为每个依赖都维护了一个小型的线程池(或者信号量)。如果该线程池已满,发往该依赖的请求就被立即拒绝,而不是排队等待,从而加速失败判定。
* 监控: Hystrix可以近乎实时地监控运行指标和配置的变化,例如成功、失败、超时、以及被拒绝的请求等。
* 回退机制:当请求失败、超时、被拒绝,或当断路器打开时,执行回退逻辑。回退逻辑由开发人员自行提供,例如返回一个缺省值。
* 自我修复:断路器打开一段时间后,会自动进入 “半开”状态。
**Maven坐标**
```xml
org.springframework.cloud
spring-cloud-starter-netflix-hystrix
```
**熔断器的隔离策略**
微服务使用Hystrix熔断器实现了服务的自动降级,让微服务具备自我保护的能力,提升了系统的稳定性,也较好的解决雪崩效应。其使用方式目前支持两种策略:
* 线程池隔离策略: 使用一个线程池来存储当前的请求,线程池对请求作处理,设置任务返回处理超时时间,堆积的请求堆积入线程池队列。这种方式需要为每个依赖的服务申请线程池,有一定的资源消耗,好处是可以应对突发流量(流量洪峰来临时,处理不完可将数据存储到线程池队里慢慢处理)
* 信号量隔离策略: 使用一个原子计数器(或信号量)来记录当前有多少个线程在运行,请求来先判断计数器的数值,若超过设置的最大线程个数则丢弃该类型的新请求,若不超过则执行计数操作请求来计数器+1,请求返回计数器-1。这种方式是严格的控制线程且立即返回模式,无法应对突发流量(流量洪峰来临时,处理的线程超过数量,其他的请求会直接返回,不继续去请求依赖的服务)
**隔离策略配置**
* hystrix.command.default.execution.isolation.strategy : 配置隔离策略
* ExecutionIsolationStrategy.SEMAPHORE 信号量隔离
* ExecutionIsolationStrategy.THREAD 线程池隔离
* hystrix.command.default.execution.isolation.maxConcurrentRequests : 最大信号量上限
### 7.2.1. 断路器聚合监控平台Turbine
在微服务架构体系中,每个服务都需要配置Hystrix DashBoard监控。如果每次只能查看单个实例的监控数据,就需要不断切换监控地址,这显然很不方便。要想看这个系统的Hystrix Dashboard数据就需要用到Hystrix Turbine。Turbine是一个聚合Hystrix 监控数据的工具,他可以将所有相关微服务的Hystrix 监控数据聚合到一起,方便使用。
**服务配置**
在application.yml的配置文件中开启turbine并进行相关配置
```yml
server:
port: 9999
spring:
application:
name: hystrix-turbine
eureka:
client:
service-url:
defaultZone: http://yuan.com:9000/eureka/
instance:
prefer-ip-address: true
turbine:
# 要监控的微服务列表,多个用,分隔
appConfig: service-order
clusterNameExpression: "'default'"
```
turbine会自动的从注册中心中获取需要监控的微服务,并聚合所有微服务中的 /hystrix.stream 数据。
**测试**
浏览器访问 http://localhost:9999/hystrix 展示HystrixDashboard。并在url位置输入 http://localhost:9999/turbine.stream ,动态根据turbine.stream数据展示多个微服务的监控数据。
## 7.3. Sentinel
随着微服务的流行,服务和服务之间的稳定性变得越来越重要。 Sentinel 以流量为切入点,从流量控制、熔断降级、系统负载保护等多个维度保护服务的稳定性。
Sentinel 具有以下特征:
* 丰富的应用场景 :Sentinel 承接了阿里巴巴近 10 年的双十一大促流量的核心场景,例如秒杀(即突发流量控制在系统容量可以承受的范围)、消息削峰填谷、集群流量控制、实时熔断下游不可用应用等。
* 完备的实时监控 :Sentinel 同时提供实时的监控功能。您可以在控制台中看到接入应用的单台机器秒级数据,甚至 500 台以下规模的集群的汇总运行情况。
* 广泛的开源生态 :Sentinel 提供开箱即用的与其它开源框架/库的整合模块,例如与 Spring Cloud、Dubbo、gRPC 的整合。您只需要引入相应的依赖并进行简单的配置即可快速地接入Sentinel。
* 完善的 SPI 扩展点:Sentinel 提供简单易用、完善的 SPI 扩展接口。您可以通过实现扩展接口来快速地定制逻辑。例如定制规则管理、适配动态数据源等。
Sentinel 的主要特性:

# 8. API网关
API网关是一个服务器,是系统对外的唯一入口。API网关封装了系统内部架构,为每个客户端提供一个定制的API。API网关方式的核心要点是,所有的客户端和消费端都通过统一的网关接入微服务,在网关层处理所有的非业务功能。网关具有的职责,如身份验证、监控、负载均衡、缓存、请求分片与管理、静态响应处理。当然,最主要的职责还是与“外界联系”。
## 8.1. Zuul
ZUUL是Netflix开源的微服务网关,它可以和Eureka、Ribbon、Hystrix等组件配合使用,Zuul组件的核心是一系列的过滤器,这些过滤器可以完成以下功能:
* 动态路由:动态将请求路由到不同后端集群
* 压力测试:逐渐增加指向集群的流量,以了解性能
* 负载分配:为每一种负载类型分配对应容量,并弃用超出限定值的请求
* 静态响应处理:边缘位置进行响应,避免转发到内部集群
* 身份认证和安全: 识别每一个资源的验证要求,并拒绝那些不符的请求。
### 8.1.1. Zuul网关限流
```xml
com.marcosbarbero.cloud
spring-cloud-zuul-ratelimit
2.4.0.RELEASE
```
```yml
##路由配置
zuul:
routes:
service-product: /service-product/**
serviceId: service-product #配置转发的微服务的服务名称
ratelimit:
key-prefix: api-ratelimite
enabled: true
repository: REDIS
behind-proxy: true
default-policy-list: #可选 - 针对所有的路由配置的策略,除非特别配置了policies
- limit: 100 #可选 - 每个刷新时间窗口对应的请求数量限制
quota: 1000 #可选- 每个刷新时间窗口对应的请求时间限制(秒)
refresh-interval: 60 # 刷新时间窗口的时间,默认值 (秒)
type: #可选 限流方式
- url
- httpmethod
- origin
policy-list: #可选 - 针对所有的路由配置的策略,除非特别配置了policies
service-product: #特定的路由
- limit: 2 #可选 - 每个刷新时间窗口对应的请求数量限制
quota: 1 #可选- 每个刷新时间窗口对应的请求时间限制(秒)
refresh-interval: 3 # 刷新时间窗口的时间,默认值 (秒)
type: #可选 限流方式
- url
- httpmethod
- origin
```
## 8.2. Spring Cloud Gateway
Spring Cloud Gateway旨在为微服务架构提供简单、有效和统一的API路由管理方式,Spring Cloud Gateway作为Spring Cloud生态系统中的网关,目标是替代Netflix Zuul,其不仅提供统一的路由方式,并且还基于Filer链的方式提供了网关基本的功能,例如:安全、监控/埋点、限流等。
## 8.3. 网关的负载均衡
采用Tengine来实现API网关的负载均衡与异常服务的自动剔除。
```nginx
upstream api-server {
server 192.168.201.1:8080;
server 192.168.201.1:8081;
check interval=3000 rise=2 fall=3 timeout=1000 type=http;
check_keepalive_requests 100;
check_http_send "HEAD / HTTP/1.1\r\nConnection: keep-alive\r\n\r\n";
check_http_expect_alive http_2xx http_3xx http_4xx;
}
server {
listen 80;
location / {
proxy_pass http://api-server;
}
}
```
# 9. Docker虚拟化容器
## 9.1. Docker命令
### 9.1.1. 容器的使用
```bash
#获取镜像
docker pull ubantu
#启动容器
docker run -it ubuntu /bin/bash
#查看所有的容器
docker ps -a
#停止一个容器
docker stop <容器 ID>
#进入容器
docker exec -it ubantu /bin/bash
```
运行参数说明:
- **-i**: 交互式操作。
- **-t**: 终端。
- **-d**:后台运行
- **ubuntu**: ubuntu 镜像。
- **/bin/bash**:放在镜像名后的是命令,这里我们希望有个交互式 Shell,因此用的是 /bin/bash。
要退出终端,直接输入 **exit**
### 9.1.2. 容器的管理
```bash
#删除容器
docker rm -f <容器 ID 或者 容器名称>
#查看容器的端口映射信息
docker port <容器 ID 或者 容器名称>
#查看容器的运行标准输出
docker logs -f <容器 ID 或者 容器名称>
#查看容器内部运行的进程
docker top <容器 ID 或者 容器名称>
#查看Docker容器的底层信息
docker inspect <容器 ID 或者 容器名称>
```
### 9.1.3. 使用Dockerfile定制镜像
TODO
# 10. 服务链路追踪
分布式链路追踪(Distributed Tracing),就是将一次分布式请求还原成调用链路,进行日志记录,性能监控并将 一次分布式请求的调用情况集中展示。比如各个服务节点上的耗时、请求具体到达哪台机器上、每个服务节点的请求状态等等。
## 10.1. Zipkin
### 10.1.1. 使用Docker安装Zipkin服务端
> 经验:经过尝试发现Zipkin 2.x的官方Docker镜像在集成MySQL运行中会抛出*ClassNotFoundException: org.ietf.jgss.GSSException*,所以建议直接使用Zipkin的Jar包部署。
**拉取镜像**
```shell
docker pull openzipkin/zipkin
docker run -id --name=zipkin -p 9411:9411 openzipkin/zipkin
#禁用ipv6
net.ipv6.conf.all.disable_ipv6 = 1
net.ipv6.conf.default.disable_ipv6 = 1
net.ipv6.conf.lo.disable_ipv6 = 1
```
**修改Zipkin配置**
1. 创建zipkin-compose.yml文件
```yml
version: '3'
services:
zipkin:
image: openzipkin/zipkin
container_name: zipkin
# 环境配置参数参考 https://github.com/openzipkin/zipkin/blob/master/zipkin-server/src/main/resources/zipkin-server-shared.yml
environment:
- STORAGE_TYPE=mysql
- MYSQL_DB=zipkin
- MYSQL_USER=youyuan
- MYSQL_PASS=youyuan
- MYSQL_HOST=192.168.199.100
- MYSQL_TCP_PORT=3306
- RABBIT_ADDRESSES=192.168.201.101:5672
- RABBIT_USER=admin
- RABBIT_PASSWORD=admin
- RABBIT_QUEUE=zipkin
ports:
- 9411:9411
```
2. 使用docker-compose运行zipkin
```shell
#安装docker-compose
#下载docker-compose
curl -L https://github.com/docker/compose/releases/download/1.25.4/docker-compose-`uname -s`-`uname -m` -o /usr/local/bin/docker-compose
#给docker-compose执行权限
chmod +x /usr/local/bin/docker-compose
#测试安装是否成功,成功的话打印出docker-compose的版本信息
docker-compose --version
#启动Zipkin
docker-compose -f zipkin-compose.yml up -d
```
### 10.1.2. 使用Zipkin的Jar包直接部署
```sh
#下载zipkin服务端jar
curl -sSL https://zipkin.io/quickstart.sh | bash -s
#运行,使用zipkin集成rabbitmq的形式运行
java -jar zipkin.jar --STORAGE_TYPE=mysql --MYSQL_DB=zipkin --MYSQL_USER=youyuan --MYSQL_PASS=youyuan --MYSQL_HOST=192.168.0.102 --MYSQL_TCP_PORT=3306 --RABBIT_ADDRESSES=192.168.201.101:5672 --RABBIT_USER=admin --RABBIT_PASSWORD=admin --RABBIT_QUEUE=zipkin
```
# 11. 消息中间件
## 11.1. RabbitMQ
1. 使用Docker安装RabbitMQ
```shell
docker pull rabbitmq:management
```
2. 运行
```shell
docker run -di --name rabbit -e RABBITMQ_DEFAULT_USER=admin -e RABBITMQ_DEFAULT_PASS=admin -p 15672:15672 -p 5672:5672 -p 25672:25672 -p 61613:61613 -p 1883:1883 rabbitmq:management
```
# 12. 分布式缓存Redis
## 12.1. Redis安装
1. 使用Docker安装Redis
```shell
docker pull redis:5.0
```
2. 运行
```shell
docker run -id --name=redis -p 6379:6379 redis:5.0
```
## 12.2. Redis可视化管理化管理
常用的Redis可视化管理工具有**RedisDesktopManager**,但是新版本需要购买,这里推荐使用开源的**AnotherRedisDesktopManager**,GitHub开源地址:https://github.com/qishibo/AnotherRedisDesktopManager。
AnotherRedisDesktopManager除了可以操作Redis的数据,还可以监控Redis使用运行情况。

## 12.3. Redis命令
| 命令及描述 |
| :----------------------------------------------------------- |
| [DEL key](https://www.runoob.com/redis/keys-del.html) 该命令用于在 key 存在时删除 key。 |
| [DUMP key](https://www.runoob.com/redis/keys-dump.html) 序列化给定 key ,并返回被序列化的值。 |
| [EXISTS key](https://www.runoob.com/redis/keys-exists.html) 检查给定 key 是否存在。 |
| [EXPIRE key](https://www.runoob.com/redis/keys-expire.html) seconds 为给定 key 设置过期时间,以秒计。 |
| [EXPIREAT key timestamp](https://www.runoob.com/redis/keys-expireat.html) EXPIREAT 的作用和 EXPIRE 类似,都用于为 key 设置过期时间。 不同在于 EXPIREAT 命令接受的时间参数是 UNIX 时间戳(unix timestamp)。 |
| [PEXPIRE key milliseconds](https://www.runoob.com/redis/keys-pexpire.html) 设置 key 的过期时间以毫秒计。 |
| [PEXPIREAT key milliseconds-timestamp](https://www.runoob.com/redis/keys-pexpireat.html) 设置 key 过期时间的时间戳(unix timestamp) 以毫秒计 |
| [KEYS pattern](https://www.runoob.com/redis/keys-keys.html) 查找所有符合给定模式( pattern)的 key 。 |
| [MOVE key db](https://www.runoob.com/redis/keys-move.html) 将当前数据库的 key 移动到给定的数据库 db 当中。 |
| [SET key value](https://www.runoob.com/redis/strings-set.html) 设置指定 key 的值 |
| [GET key](https://www.runoob.com/redis/strings-get.html) 获取指定 key 的值。 |
| [GETRANGE key start end](https://www.runoob.com/redis/strings-getrange.html) 返回 key 中字符串值的子字符 |
| [GETSET key value](https://www.runoob.com/redis/strings-getset.html) 将给定 key 的值设为 value ,并返回 key 的旧值(old value)。 |
| [GETBIT key offset](https://www.runoob.com/redis/strings-getbit.html) 对 key 所储存的字符串值,获取指定偏移量上的位(bit)。 |
| [MGET key1 [key2..\]](https://www.runoob.com/redis/strings-mget.html) 获取所有(一个或多个)给定 key 的值。 |
| [SETBIT key offset value](https://www.runoob.com/redis/strings-setbit.html) 对 key 所储存的字符串值,设置或清除指定偏移量上的位(bit)。 |
| [SETEX key seconds value](https://www.runoob.com/redis/strings-setex.html) 将值 value 关联到 key ,并将 key 的过期时间设为 seconds (以秒为单位)。 |
| [SETNX key value](https://www.runoob.com/redis/strings-setnx.html) 只有在 key 不存在时设置 key 的值。 |
| [SETRANGE key offset value](https://www.runoob.com/redis/strings-setrange.html) 用 value 参数覆写给定 key 所储存的字符串值,从偏移量 offset 开始。 |
| [STRLEN key](https://www.runoob.com/redis/strings-strlen.html) 返回 key 所储存的字符串值的长度。 |
| [MSET key value [key value ...\]](https://www.runoob.com/redis/strings-mset.html) 同时设置一个或多个 key-value 对。 |
| [MSETNX key value [key value ...\]](https://www.runoob.com/redis/strings-msetnx.html) 同时设置一个或多个 key-value 对,当且仅当所有给定 key 都不存在。 |
| [PSETEX key milliseconds value](https://www.runoob.com/redis/strings-psetex.html) 这个命令和 SETEX 命令相似,但它以毫秒为单位设置 key 的生存时间,而不是像 SETEX 命令那样,以秒为单位。 |
| [INCR key](https://www.runoob.com/redis/strings-incr.html) 将 key 中储存的数字值增一。 |
| [INCRBY key increment](https://www.runoob.com/redis/strings-incrby.html) 将 key 所储存的值加上给定的增量值(increment) 。 |
| [INCRBYFLOAT key increment](https://www.runoob.com/redis/strings-incrbyfloat.html) 将 key 所储存的值加上给定的浮点增量值(increment) 。 |
| [DECR key](https://www.runoob.com/redis/strings-decr.html) 将 key 中储存的数字值减一。 |
| [DECRBY key decrement](https://www.runoob.com/redis/strings-decrby.html) key 所储存的值减去给定的减量值(decrement) 。 |
| [APPEND key value](https://www.runoob.com/redis/strings-append.html) 如果 key 已经存在并且是一个字符串, APPEND 命令将指定的 value 追加到该 key 原来值(value)的末尾。 |
## 12.4. Redis列表
Redis列表是简单的字符串列表,按照插入顺序排序。你可以添加一个元素到列表的头部(左边)或者尾部(右边)
一个列表最多可以包含 2^32^ - 1 个元素 (4294967295, 每个列表超过40亿个元素)。
| 命令及描述 |
| :----------------------------------------------------------- |
| [BLPOP key1 [key2 \] timeout](https://www.runoob.com/redis/lists-blpop.html) 移出并获取列表的第一个元素, 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。 |
| [BRPOP key1 [key2 \] timeout](https://www.runoob.com/redis/lists-brpop.html) 移出并获取列表的最后一个元素, 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。 |
| [BRPOPLPUSH source destination timeout](https://www.runoob.com/redis/lists-brpoplpush.html) 从列表中弹出一个值,将弹出的元素插入到另外一个列表中并返回它; 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。 |
| [LINDEX key index](https://www.runoob.com/redis/lists-lindex.html) 通过索引获取列表中的元素 |
| [LINSERT key BEFORE\|AFTER pivot value](https://www.runoob.com/redis/lists-linsert.html) 在列表的元素前或者后插入元素 |
| [LLEN key](https://www.runoob.com/redis/lists-llen.html) 获取列表长度 |
| [LPOP key](https://www.runoob.com/redis/lists-lpop.html) 移出并获取列表的第一个元素 |
| [LPUSH key value1 [value2\]](https://www.runoob.com/redis/lists-lpush.html) 将一个或多个值插入到列表头部 |
| [LPUSHX key value](https://www.runoob.com/redis/lists-lpushx.html) 将一个值插入到已存在的列表头部 |
| [LRANGE key start stop](https://www.runoob.com/redis/lists-lrange.html) 获取列表指定范围内的元素 |
| [LREM key count value](https://www.runoob.com/redis/lists-lrem.html) 移除列表元素 |
| [LSET key index value](https://www.runoob.com/redis/lists-lset.html) 通过索引设置列表元素的值 |
| [LTRIM key start stop](https://www.runoob.com/redis/lists-ltrim.html) 对一个列表进行修剪(trim),就是说,让列表只保留指定区间内的元素,不在指定区间之内的元素都将被删除。 |
| [RPOP key](https://www.runoob.com/redis/lists-rpop.html) 移除列表的最后一个元素,返回值为移除的元素。 |
| [RPOPLPUSH source destination](https://www.runoob.com/redis/lists-rpoplpush.html) 移除列表的最后一个元素,并将该元素添加到另一个列表并返回 |
| [RPUSH key value1 [value2\]](https://www.runoob.com/redis/lists-rpush.html) 在列表中添加一个或多个值 |
| [RPUSHX key value](https://www.runoob.com/redis/lists-rpushx.html) 为已存在的列表添加值 |
## 12.5. Redis哈希
Redis hash 是一个 string 类型的 field(字段) 和 value(值) 的映射表,hash 特别适合用于存储对象。
Redis 中每个 hash 可以存储 2^32^ - 1 键值对(40多亿)。
| 命令及描述 |
| :----------------------------------------------------------- |
| [HDEL key field1 [field2\]](https://www.runoob.com/redis/hashes-hdel.html) 删除一个或多个哈希表字段 |
| [HEXISTS key field](https://www.runoob.com/redis/hashes-hexists.html) 查看哈希表 key 中,指定的字段是否存在。 |
| [HGET key field](https://www.runoob.com/redis/hashes-hget.html) 获取存储在哈希表中指定字段的值。 |
| [HGETALL key](https://www.runoob.com/redis/hashes-hgetall.html) 获取在哈希表中指定 key 的所有字段和值 |
| [HINCRBY key field increment](https://www.runoob.com/redis/hashes-hincrby.html) 为哈希表 key 中的指定字段的整数值加上增量 increment 。 |
| [HINCRBYFLOAT key field increment](https://www.runoob.com/redis/hashes-hincrbyfloat.html) 为哈希表 key 中的指定字段的浮点数值加上增量 increment 。 |
| [HKEYS key](https://www.runoob.com/redis/hashes-hkeys.html) 获取所有哈希表中的字段 |
| [HLEN key](https://www.runoob.com/redis/hashes-hlen.html) 获取哈希表中字段的数量 |
| [HMGET key field1 [field2\]](https://www.runoob.com/redis/hashes-hmget.html) 获取所有给定字段的值 |
| [HMSET key field1 value1 [field2 value2 \]](https://www.runoob.com/redis/hashes-hmset.html) 同时将多个 field-value (域-值)对设置到哈希表 key 中。 |
| [HSET key field value](https://www.runoob.com/redis/hashes-hset.html) 将哈希表 key 中的字段 field 的值设为 value 。 |
| [HSETNX key field value](https://www.runoob.com/redis/hashes-hsetnx.html) 只有在字段 field 不存在时,设置哈希表字段的值。 |
| [HVALS key](https://www.runoob.com/redis/hashes-hvals.html) 获取哈希表中所有值。 |
| [HSCAN key cursor [MATCH pattern\] [COUNT count]](https://www.runoob.com/redis/hashes-hscan.html) 迭代哈希表中的键值对。 |
## 12.6. Redis集合
Redis 的 Set 是 String 类型的无序集合。集合成员是唯一的,这就意味着集合中不能出现重复的数据。
Redis 中集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是 O(1)。
集合中最大的成员数为 2^32^ - 1 (4294967295, 每个集合可存储40多亿个成员)。
| 命令及描述 |
| :----------------------------------------------------------- |
| [SADD key member1 [member2\]](https://www.runoob.com/redis/sets-sadd.html) 向集合添加一个或多个成员 |
| [SCARD key](https://www.runoob.com/redis/sets-scard.html) 获取集合的成员数 |
| [SDIFF key1 [key2\]](https://www.runoob.com/redis/sets-sdiff.html) 返回第一个集合与其他集合之间的差异。 |
| [SDIFFSTORE destination key1 [key2\]](https://www.runoob.com/redis/sets-sdiffstore.html) 返回给定所有集合的差集并存储在 destination 中 |
| [SINTER key1 [key2\]](https://www.runoob.com/redis/sets-sinter.html) 返回给定所有集合的交集 |
| [SINTERSTORE destination key1 [key2\]](https://www.runoob.com/redis/sets-sinterstore.html) 返回给定所有集合的交集并存储在 destination 中 |
| [SISMEMBER key member](https://www.runoob.com/redis/sets-sismember.html) 判断 member 元素是否是集合 key 的成员 |
| [SMEMBERS key](https://www.runoob.com/redis/sets-smembers.html) 返回集合中的所有成员 |
| [SMOVE source destination member](https://www.runoob.com/redis/sets-smove.html) 将 member 元素从 source 集合移动到 destination 集合 |
| [SPOP key](https://www.runoob.com/redis/sets-spop.html) 移除并返回集合中的一个随机元素 |
| [SRANDMEMBER key [count\]](https://www.runoob.com/redis/sets-srandmember.html) 返回集合中一个或多个随机数 |
| [SREM key member1 [member2\]](https://www.runoob.com/redis/sets-srem.html) 移除集合中一个或多个成员 |
| [SUNION key1 [key2\]](https://www.runoob.com/redis/sets-sunion.html) 返回所有给定集合的并集 |
| [SUNIONSTORE destination key1 [key2\]](https://www.runoob.com/redis/sets-sunionstore.html) 所有给定集合的并集存储在 destination 集合中 |
| [SSCAN key cursor [MATCH pattern\] [COUNT count]](https://www.runoob.com/redis/sets-sscan.html) 迭代集合中的元素 |
## 12.7. Redis有序集合
Redis 有序集合和集合一样也是string类型元素的集合,且不允许重复的成员。
不同的是每个元素都会关联一个double类型的分数。redis正是通过分数来为集合中的成员进行从小到大的排序。
有序集合的成员是唯一的,但分数(score)却可以重复。
集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是O(1)。 集合中最大的成员数为 2^32^ - 1 (4294967295, 每个集合可存储40多亿个成员)。
| 命令及描述 |
| :----------------------------------------------------------- |
| [ZADD key score1 member1 [score2 member2\]](https://www.runoob.com/redis/sorted-sets-zadd.html) 向有序集合添加一个或多个成员,或者更新已存在成员的分数 |
| [ZCARD key](https://www.runoob.com/redis/sorted-sets-zcard.html) 获取有序集合的成员数 |
| [ZCOUNT key min max](https://www.runoob.com/redis/sorted-sets-zcount.html) 计算在有序集合中指定区间分数的成员数 |
| [ZINCRBY key increment member](https://www.runoob.com/redis/sorted-sets-zincrby.html) 有序集合中对指定成员的分数加上增量 increment |
| [ZINTERSTORE destination numkeys key [key ...\]](https://www.runoob.com/redis/sorted-sets-zinterstore.html) 计算给定的一个或多个有序集的交集并将结果集存储在新的有序集合 key 中 |
| [ZLEXCOUNT key min max](https://www.runoob.com/redis/sorted-sets-zlexcount.html) 在有序集合中计算指定字典区间内成员数量 |
| [ZRANGE key start stop [WITHSCORES\]](https://www.runoob.com/redis/sorted-sets-zrange.html) 通过索引区间返回有序集合指定区间内的成员 |
| [ZRANGEBYLEX key min max [LIMIT offset count\]](https://www.runoob.com/redis/sorted-sets-zrangebylex.html) 通过字典区间返回有序集合的成员 |
| [ZRANGEBYSCORE key min max [WITHSCORES\] [LIMIT]](https://www.runoob.com/redis/sorted-sets-zrangebyscore.html) 通过分数返回有序集合指定区间内的成员 |
| [ZRANK key member](https://www.runoob.com/redis/sorted-sets-zrank.html) 返回有序集合中指定成员的索引 |
| [ZREM key member [member ...\]](https://www.runoob.com/redis/sorted-sets-zrem.html) 移除有序集合中的一个或多个成员 |
| [ZREMRANGEBYLEX key min max](https://www.runoob.com/redis/sorted-sets-zremrangebylex.html) 移除有序集合中给定的字典区间的所有成员 |
| [ZREMRANGEBYRANK key start stop](https://www.runoob.com/redis/sorted-sets-zremrangebyrank.html) 移除有序集合中给定的排名区间的所有成员 |
| [ZREMRANGEBYSCORE key min max](https://www.runoob.com/redis/sorted-sets-zremrangebyscore.html) 移除有序集合中给定的分数区间的所有成员 |
| [ZREVRANGE key start stop [WITHSCORES\]](https://www.runoob.com/redis/sorted-sets-zrevrange.html) 返回有序集中指定区间内的成员,通过索引,分数从高到低 |
| [ZREVRANGEBYSCORE key max min [WITHSCORES\]](https://www.runoob.com/redis/sorted-sets-zrevrangebyscore.html) 返回有序集中指定分数区间内的成员,分数从高到低排序 |
| [ZREVRANK key member](https://www.runoob.com/redis/sorted-sets-zrevrank.html) 返回有序集合中指定成员的排名,有序集成员按分数值递减(从大到小)排序 |
| [ZSCORE key member](https://www.runoob.com/redis/sorted-sets-zscore.html) 返回有序集中,成员的分数值 |
| [ZUNIONSTORE destination numkeys key [key ...\]](https://www.runoob.com/redis/sorted-sets-zunionstore.html) 计算给定的一个或多个有序集的并集,并存储在新的 key 中 |
| [ZSCAN key cursor [MATCH pattern\] [COUNT count]](https://www.runoob.com/redis/sorted-sets-zscan.html) 迭代有序集合中的元素(包括元素成员和元素分值) |
## 12.8. HyperLogLog
Redis 在 2.8.9 版本添加了 HyperLogLog 结构。
Redis HyperLogLog 是用来做基数统计的算法,HyperLogLog 的优点是,在输入元素的数量或者体积非常非常大时,计算基数所需的空间总是固定 的、并且是很小的。
在 Redis 里面,每个 HyperLogLog 键只需要花费 12 KB 内存,就可以计算接近 2^64 个不同元素的基 数。这和计算基数时,元素越多耗费内存就越多的集合形成鲜明对比。
但是,因为 HyperLogLog 只会根据输入元素来计算基数,而不会储存输入元素本身,所以 HyperLogLog 不能像集合那样,返回输入的各个元素。
> 什么是基数?
>
> 比如数据集 {1, 3, 5, 7, 5, 7, 8}, 那么这个数据集的基数集为 {1, 3, 5 ,7, 8}, 基数(不重复元素)为5。
>
> 基数估计就是在误差可接受的范围内,快速计算基数。
| 命令及描述 |
| :----------------------------------------------------------- |
| [PFADD key element [element ...\]](https://www.runoob.com/redis/hyperloglog-pfadd.html) 添加指定元素到 HyperLogLog 中。 |
| [PFCOUNT key [key ...\]](https://www.runoob.com/redis/hyperloglog-pfcount.html) 返回给定 HyperLogLog 的基数估算值。 |
| [PFMERGE destkey sourcekey [sourcekey ...\]](https://www.runoob.com/redis/hyperloglog-pfmerge.html) 将多个 HyperLogLog 合并为一个 HyperLogLog |
## 12.9. Redis 事务
Redis 事务可以一次执行多个命令, 并且带有以下三个重要的保证:
- 批量操作在发送 EXEC 命令前被放入队列缓存。
- 收到 EXEC 命令后进入事务执行,事务中任意命令执行失败,其余的命令依然被执行。
- 在事务执行过程,其他客户端提交的命令请求不会插入到事务执行命令序列中。
一个事务从开始到执行会经历以下三个阶段:
- 开始事务。
- 命令入队。
- 执行事务。
Redis 事务以 **MULTI** 开始一个事务, 然后将多个命令入队到事务中, 最后由 **EXEC** 命令触发事务, 一并执行事务中的所有命令。
**单个 Redis 命令的执行是原子性的,但 Redis 没有在事务上增加任何维持原子性的机制,所以 Redis 事务的执行并不是原子性的。**
事务可以理解为一个打包的批量执行脚本,但批量指令并非原子化的操作,中间某条指令的失败不会导致前面已做指令的回滚,也不会造成后续的指令不做。
## 12.10. Redis 脚本
Redis 脚本使用 Lua 解释器来执行脚本。 Redis 2.6 版本通过内嵌支持 Lua 环境。执行脚本的常用命令为 **EVAL**。
Eval 命令的基本语法如下:
```shell
redis 127.0.0.1:6379> EVAL script numkeys key [key ...] arg [arg ...]
```
## 12.11. Java使用Redis
Java的Redis客户端常用的是Jedis和Redisson。Jedis提供了完整Redis命令,而Redisson有更多分布式的容器实现。在微服务架构中,使用Redisson可以非常简便的完成许多分布式操作。
### 12.11.1. Redis的Key命名规范
Key命名规则:
```
系统:业务模块:功能:数据类型:key
```
示例:
```
USER:TEST:RLOCK:s%:userAdd
```
### 12.11.2. Redisson项目介绍
[Redisson](https://redisson.org/)是架设在[Redis](http://redis.cn/)基础上的一个Java驻内存数据网格(In-Memory Data Grid)。充分的利用了Redis键值数据库提供的一系列优势,基于Java实用工具包中常用接口,为使用者提供了一系列具有分布式特性的常用工具类。使得原本作为协调单机多线程并发程序的工具包获得了协调分布式多机多线程并发系统的能力,大大降低了设计和研发大规模分布式系统的难度。同时结合各富特色的分布式服务,更进一步简化了分布式环境中程序相互之间的协作。
* 文档:[Redisson文档](https://github.com/redisson/redisson/wiki/Redisson项目介绍)
# 13. 分布式配置中心
对于传统的单体应用而言,常使用配置文件来管理所有配置,比如SpringBoot的application.yml文件,但是在微服务架构中全部手动修改的话不易维护。微服务的配置管理一般有以下需求:
* 集中配置管理,一个微服务架构中可能有成百上千个微服务,所以集中配置管理是很重要的。
* 不同环境不同配置,比如数据源配置在不同环境(开发,生产,测试)中是不同的。
* 运行期间可动态调整。例如,可根据各个微服务的负载情况,动态调整数据源连接池大小等
* 配置修改后可自动更新。如配置内容发生变化,微服务可以自动更新配置
综上所述对于微服务架构而言,一套统一通用的管理配置机制是不可缺少的重要组成部分。常见的做法就是通过配置中心进行管理。
## 13.1. Spring Cloud Config
Spring Cloud Config Server是一个可横向扩展、集中式的配置服务器,它用于集中管理应用程序各个环境下的配置,默认使用Git存储配置文件内容,也可以使用SVN存储,或者是本地文件存储。
## 13.2. Apollo
### 13.2.1. 简介
Apollo(阿波罗)是携程框架部门研发的分布式配置中心,能够集中化管理应用不同环境、不同集群的配置,配置修改后能够实时推送到应用端,并且具备规范的权限、流程治理等特性,适用于微服务配置管理场景。
Apollo目前提供了以下的特性:
* **统一管理不同环境、不同集群的配置**
- Apollo提供了一个统一界面集中式管理不同环境(environment)、不同集群(cluster)、不同命名空间(namespace)的配置
- 同一份代码部署在不同的集群,可以有不同的配置,比如zookeeper的地址等
- 通过命名空间(namespace)可以很方便地支持多个不同应用共享同一份配置,同时还允许应用对共享的配置进行覆盖
- 配置修改实时生效(热发布)
* **版本发布管理**
- 所有的配置发布都有版本概念,从而可以方便地支持配置的回滚
* **灰度发布**
- 支持配置的灰度发布,比如点了发布后,只对部分应用实例生效,等观察一段时间没问题后再推给所有应用实例
* **权限管理、发布审核、操作审计**
* 应用和配置的管理都有完善的权限管理机制,对配置的管理还分为了编辑和发布两个环节,从而减少人为的错误
* 所有的操作都有审计日志,可以方便地追踪问题
* **客户端配置信息监控**
* 可以在界面上方便地看到配置在被哪些实例使用
### 13.2.2. 部署策略
分布式部署需要事先确定部署的环境以及部署方式。
Apollo目前支持以下环境:
- DEV:开发环境
- FAT:测试环境,相当于alpha环境(功能测试)
- UAT:集成环境,相当于beta环境(回归测试)
- PRO:生产环境
部署策略如下:
- Portal部署在生产环境的机房,通过它来直接管理FAT、UAT、PRO等环境的配置
- Meta Server、Config Service和Admin Service在每个环境都单独部署,使用独立的数据库
- Meta Server、Config Service和Admin Service在生产环境部署在两个机房,实现双活
- Meta Server和Config Service部署在同一个JVM进程内,Admin Service部署在同一台服务器的另一个JVM进程内
# 14. 消息总线
在微服务架构中,通常会使用轻量级的消息代理来构建一个共用的消息主题来连接各个微服务实例,它广播的消息会被所有在注册中心的微服务实例监听和消费,也称消息总线。
## 14.1. Spring Cloud Bus
# 15. Spring Cloud Stream
## 15.1. 概述
Spring Cloud Stream由一个中间件中立的核组成。应用通过Spring Cloud Stream的input和output通道与外界交流。通道通过指定中间件的Binder实现与外部代理连接。业务开发者不再关注具体消息中间件,只需关注Binder对应用程序提供的抽象概念来使用消息中间件实现业务即可。
* input (相当于消费者consumer,从队列中接收消息)
* output (相当于生产者producer,从队列中发送消息)
## 15.2. 消息分组
通常在生产环境,我们的每个服务都不会以单节点的方式运行在生产环境,当同一个服务启动多个实例的时候,这些实例都会绑定到同一个消息通道的目标主题(Topic)上。默认情况下,当生产者发出一条消息到绑定通道上,这条消息会产生多个副本被每个消费者实例接收和处理,但是**有些业务场景之下,我们希望生产者产生的消息只被其中一个实例消费,这个时候我们需要为这些消费者设置消费组来实现这样的功能。**
实现的方式非常简单,我们只需要在服务消费者端设置spring.cloud.stream.bindings.input.group 属性即可,在同一个group中的多个消费者只有一个可以获取到消息并消费。比如我们可以这样实现:
```yaml
server:
port: 7002 #服务端口
spring:
application:
name: stream-consumer #指定服务名
rabbitmq:
addresses: 192.168.201.101
port: 5672
username: admin
password: admin
cloud:
stream:
bindings:
input: #内置的获取消息的通道 , 从springcloud-producer中获取消息
destination: springcloud-producer
group: group-1 # 设置消息分组,在同一个group中的多个消费者只有一个可以获取到消息并消费
binders:
defaultRabbit:
type: rabbit
```
# 16. 系统负载均衡
## 16.1. Nginx
Nginx是一个高性能的HTTP和反向代理web服务器,同时也提供了IMAP/POP3/SMTP服务。
## 16.2. Tengine
Tengine是由淘宝网发起的Web服务器项目。它在[Nginx](http://nginx.org/)的基础上,针对大访问量网站的需求,添加了很多高级功能和特性。Tengine的性能和稳定性已经在大型的网站如淘宝网,天猫商城等得到了很好的检验。它的最终目标是打造一个高效、稳定、安全、易用的Web平台。
## 16.3. Haproxy
### 16.3.1. Haproxy简介
HAProxy提供**高可用性**、**负载均衡**以及基于TCP和HTTP应用的代理,**支持虚拟主机**,它是免费、快速并且可靠的一种解决方案。HAProxy特别适用于那些负载特大的web站点, 这些站点通常又需要会话保持或七层处理。HAProxy运行在当前的硬件上,完全可以支持数以万计的并发连接。并且它的运行模式使得它可以很简单安全的整合进您当前的架构中, 同时可以保护你的web服务器不被暴露到网络上。
HAProxy实现了一种**事件驱动**, **单一进程**模型,此模型支持非常大的并发连接数。多进程或多线程模型受内存限制 、系统调度器限制以及无处不在的锁限制,很少能处理数千并发连接。事件驱动模型因为在有更好的资源和时间管理的用户端(User-Space) 实现所有这些任务,所以没有这些问题。
### 16.3.2. Haproxy和Nginx的区别
**Nginx的优点:**
1)优点工作在OSI第7层,可以针对http应用做一些分流的策略
2)Nginx对网络的依赖非常小,理论上能ping通就能进行负载功能
3)Nginx安装和配置比较简单
4)可以承担搞的负载压力且稳定
5)Nginx可以通过端口检查到服务器内部的故障
6)Nginx不仅仅是一款优秀的负载均衡/反向代理软件,它同时也是功能强大的Web应用服务器
**Nginx的缺点:**
1)Nginx不支持url来检测
2)Nginx仅能支持http、https和Email,这个它的弱势
3)Nginx的Session的保持,Cookie的引导能力相对欠缺
**HaProxy的优点:**
1)HaProxy是支持虚拟主机的
2)支持Url检测后端的服务器
3)它跟LVS一样,本身仅仅就只是一款负载均衡软件;单纯从效率上来讲HaProxy更会比Nginx有更出色的负载均衡速度,在并发处理上也是优于Nginx的;
4)HaProxy可以对Mysql读进行负载均衡,节点检测
5)HaProxy的算法较多,支持8种负载均衡算法,同时支持session会话保持
6)HaProxy有免费开源的管理后台,Nginx的管理后台要收费
# 17. 代码质量管理
## 17.1. SonarQube
# 18. 接口负载测试
## 18.1. JMeter
# 19. 代码设计
## 19.1. 系统配置加密
为了进一步提升服务器信息的安全性,可以对数据库账号、服务密钥等敏感配置进行加密(例如加密正式环境的配置信息),在服务运行时通过运行时参数设置解密密码,确保即使配置文件被其他人知晓也不会直接影响到服务器的安全。
### 19.1.1. 使用Jasypt加密配置信息
#### 19.1.1.1. 引入Jasypt依赖
```xml
com.github.ulisesbocchio
jasypt-spring-boot-starter
${jasypt-spring-boot-starter.version}
```
#### 19.1.1.2. 加密参数获取密文
```java
@Test
public void encryptTest() {
String pwd = "YouYuan_SpringCloud_pwd";
PooledPBEStringEncryptor encryptor = new PooledPBEStringEncryptor();
SimpleStringPBEConfig config = new SimpleStringPBEConfig();
config.setPassword(pwd);
config.setAlgorithm("PBEWITHHMACSHA512ANDAES_256");
config.setPoolSize("1");
config.setIvGeneratorClassName("org.jasypt.iv.RandomIvGenerator");
config.setStringOutputType("base64");
encryptor.setConfig(config);
String rUsername = "youyuan";
String rPassword = "youyuan";
String encUsername = encryptor.encrypt(rUsername);
String encPassword = encryptor.encrypt(rPassword);
System.out.println("encUsername:"+encUsername);
System.out.println("encPassword:"+encPassword);
}
```
#### 19.1.1.3. 将配置文件改为加密密文
```yaml
#正式环境使用运行时参数指定密码 -Djasypt.encryptor.password="password"
jasypt:
encryptor:
password: "YouYuan_SpringCloud_pwd"
spring:
application:
name: service-user
datasource:
url: jdbc:mysql://localhost:3306/spring-cloud-study?useUnicode=true&characterEncoding=utf8&serverTimezone=UTC
username: ENC(eG/Z3s2wMBdyTh0DkdIB8WkQ8kgZsmUpbXoJIiyHEQAvuGHzOP+/Bu+go3JtHZgE)
password: ENC(9Lr0Vx74NAedr3py9yz4dcHXThAE6ShWo1TKBfB2MbXwj+OxvkXl5wa1oS/g9y6C)
```
## 19.2. 对象转换性能优化
集成Mapstruct优雅的进行对象转换,通过生成代码避免反射来提升对象转换的性能。
# 20. 系统设计
## 20.1. 监控SQL服务端执行
### 20.1.1. 使用P6Spy监控SQL与慢查询
1. 引入P6Spy依赖
```xml
p6spy
p6spy
${p6spy.version}
```
2. 替换数据源驱动
```yaml
spring:
datasource:
driver-class-name: com.p6spy.engine.spy.P6SpyDriver
url: jdbc:p6spy:mysql://localhost:3306/spring-cloud-study?useUnicode=true&characterEncoding=utf8&serverTimezone=UTC
```
3. 配置P6Spy配置文件spy.properries
```properties
#3.2.1以上使用
modulelist=com.baomidou.mybatisplus.extension.p6spy.MybatisPlusLogFactory,com.p6spy.engine.outage.P6OutageFactory
# 自定义日志打印
logMessageFormat=com.baomidou.mybatisplus.extension.p6spy.P6SpyLogger
#日志输出到控制台
appender=com.baomidou.mybatisplus.extension.p6spy.StdoutLogger
# 使用日志系统记录 sql
#appender=com.p6spy.engine.spy.appender.Slf4JLogger
# 设置 p6spy driver 代理
deregisterdrivers=true
# 取消JDBC URL前缀
useprefix=true
# 配置记录 Log 例外,可去掉的结果集有error,info,batch,debug,statement,commit,rollback,result,resultset.
excludecategories=info,debug,result,commit,resultset
# 日期格式
dateformat=yyyy-MM-dd HH:mm:ss
# 实际驱动可多个
#driverlist=org.h2.Driver
# 是否开启慢SQL记录
outagedetection=true
# 慢SQL记录标准 2 秒
outagedetectioninterval=2
```
### 使用Druid监控服务运行情况
TODO
# 21. 注意事项
## 21.1. Docker部署注意事项
1. **==要将Linux系统防火墙设置为关闭,或者将Redis、Rabbit等服务端口添加到防火墙开放名单中,否则服务在外部无法访问。==**
```shell
#停止防火墙服务
systemctl stop firewalld.service
#开启一个端口
firewall-cmd --zone=public --add-port=80/tcp --permanent #--permanent 永久生效,没有此参数重启后失效
firewall-cmd --reload #重新载入防火墙
```
# 22. Grafana系统监控
Grafana是一个开源的度量分析与可视化套件。经常被用作基础设施的时间序列数据和应用程序分析的可视化。Grafana支持许多不同的数据源。
* Grafana:通过将采集的数据查询然后可视化的展示
* Prometheus:存储的是时序数据,即按相同时序(相同名称和标签),以时间维度存储连续的数据的集合
* Exporter:它是prometheus监控中重要的组成部分,负责数据指标的采集
Prometheus官方提供的exporter有blackbox_exporter、consul_exporter、graphite_exporter、haproxy_exporter、memcached_exporter、mysqld_exporter、node_exporter、statsd_exporter,还有很多第三方的exporter,例如:Redis exporter、RabbitMQ exporter等。
监控Java程序可以使用jmx_exporter,github地址:https://github.com/prometheus/jmx_exporter