1 前后端分离

2 使用docker准备数据库

00.介绍
    docker优势:通过命令可以实现各种服务的部署功能
    docker基于linux:一般建议centos7,centos6不稳定
    docker要求centos7内核版本>=3.10,查看系统内核#uname -r

01.准备docker容器
	a.下载docker
		yum -y install docker
	b.启动
		systemctl start docker
	c.开启自启
		systemctl enable docker
		
02.在docker容器中安装各个服务镜像
	a.下载mysql镜像
		docker pull 镜像名字:版本号
		docker pull mysql:5.5.61d
		docker pull registry.docker-cn.com/mysql:5.5.61
		docker pull daocloud.io/library/mysql:5.5.61
	b.安装mysql镜像
		docker run -di --name=micro_mysql -p 3306:3306 -e MYSQL_ROOT_PASSWORD=4023615 daocloud.io/library/mysql:5.5.61
    c.检查安装的镜像
        docker ps -a
	d.命令mysql
		docker start 80576e5ea74a			--启动
		docker stop 80576e5ea74a			--停止
		docker restart 80576e5ea74a			--重启
		docker rm 80576e5ea74a				--卸载
	e.进入容器bash
		docker exec -it micro_mysql bash

3 SpringBoot整合SSM / SSS

3.1 micro_city:springboot+ssm+注解

3.2 micro_city2:springboot+ssm+SQL映射文件

3.3 micro_city3:springboot+ssm+外部配置文件

3.4 micro_city4:springboot+ssm+配置类

3.5 micro_person:springboot+ss(springdata+jpa)

4 SpringBoot整合HttpClient

4.1 单体框架HttpClient

4.2 SpringBoot整合HttpClient

5 Jsoup解析html代码

5.1 普通、通用、复合选择器

5.2 Jsoup三种加载方式

6 爬虫案例

6.1 爬虫=HttpClient+Jsoup

6.2 WebMagic爬虫框架

7 Redis数据库

7.1 redis环境搭建

01.打开目录、下载、解压、重命名
	a.打开目录
		cd /usr
	b.下载
		wget http://download.redis.io/releases/redis-6.0.1.tar.gz
	c.解压
		tar -zxvf redis-6.0.1.tar.gz
	d.重命名
		mv redis-6.0.1 redis

02.配置文件前需要编译
	a.打开目录
		cd redis/
	b.编译
		make
	c.编译make后,如果报错/bin/sh: cc: command not found
		yum install gcc-c++
	d.安装c++后,如果报错error: jemalloc/jemalloc.h: No such file or directory
		make MALLOC=libc
	e.再次尝试后,如果报错‘struct redisServer’ has no member named ‘cached_master’
		可能gcc(4.8.5)与redis(4.x)版本冲突
	f.升级gcc
		gcc -v
		yum -y install centos-release-scl
		yum -y install devtoolset-9-gcc devtoolset-9-gcc-c++ devtoolset-9-binutils
		scl enable devtoolset-9 bash
	g.再次尝试
		make MALLOC=libc
		
03.配置redis.conf
	daemonize yes
	
04.测试
	a.打开目录
		cd /usr/redis
	b.启动服务端,默认端口6379
		src/redis-server redis.conf
	c.启动客户端连接服务端
		src/redis-cli -p 6379

7.2 Jedis环境搭建

01.配置redis.conf,绑定端口
	bind 192.168.2.128 127.0.0.1

02.启动服务端,默认端口6379
	src/redis-server redis.conf
	
03.测试Jedis
	a.引入jedis依赖
		<dependency>
        	<groupId>redis.clients</groupId>
        	<artifactId>jedis</artifactId>
        	<version>3.1.0</version>
    	</dependency>
	b.测试代码
        public static void main(String[] args) {
            Jedis jedis = new Jedis("192.168.2.128", 6379);
            System.out.println(jedis.ping());
        }

7.3 Redis数据库

7.3.1 字符串Strings

7.3.2 列表Lists

7.3.3 集合Sets

7.3.4 有序集合Sorted Sets

7.3.5 散列Hashes

7.4 Redis存储/读取爬虫数据

8 MongoDB数据库

8.1 MongoDB环境搭建

8.2 MongoDB数据库

8.3 MongoDB存储/读取爬虫数据

9 Hbase数据库

9.1 Hbase架构设计

9.2 Hbase环境搭建

9.2.1 MapReduce完全分布式

01.安装hadoop
	a.上传、解压、重命名
		cd /usr/local
		tar -zxvf hadoop-2.9.2.tar.gz
		mv hadoop-2.9.2 hadoop292
	b.配置环境变量
		vi /etc/profile
		export HADOOP_HOME=/usr/local/hadoop292
		export PATH=$HADOOP_HOME/bin:$PATH
	c.刷新环境变量
		source /etc/profile
	
02.配置hadoop /usr/local/hadoop292/etc/hadoop,共7个
	1.slaves:配置从节点
		bigdata02
		bigdata03
	2.hadoop-env.sh:追加JAVA_HOME
		export JAVA_HOME=/usr/java/jdk1.8.0_231-amd64
	3.yarn-env.sh:追加JAVA_HOME
		export JAVA_HOME=/usr/java/jdk1.8.0_231-amd64
	4.core-site.xml
        <configuration>
            <!--hdfs -->
            <property>
                <name>fs.defaultFS</name>
                <value>hdfs://bigdata01:9000</value>
            </property>
            <property>
                <name>hadoop.tmp.dir</name>
                <value>file:/usr/local/hadoop292/temp</value>
            </property>
        </configuration>
	5.hdfs-site.xml
		<configuration>
            <property>
                <name>dfs.namenode.secondary.http-address</name>
                <value>bigdata01:50090</value>
            </property>
            <property>
                <name>dfs.namenode.name.dir</name>
                <value>file:/usr/local/hadoop292/temp/dfs/name</value>
            </property>
            <property>
                <name>dfs.namenode.data.dir</name>
                <value>file:/usr/local/hadoop292/temp/dfs/data</value>
            </property>
            <property>
                <name>dfs.replication</name>
                <value>2</value>
            </property>
            <property>
                <name>dfs.webhdfs.enabled</name>
                <value>true</value>
            </property>
            <property>
                <name>dfs.permissions</name>
                <value>false</value>
            </property>
        </configuration>
	6.mapred-site.xml
		<configuration>
            <property>
                <name>mapreduce.framework.name</name>
                <value>yarn</value>
            </property>
            <property>
                <name>mapreduce.jobhistory.address</name>
                <value>bigdata01:10020</value>
            </property>

            <property>
                <name>mapreduce.jobhistory.webapp.address</name>
                <value>bigdata01:19888</value>
            </property>
        </configuration>
	7.yarn-site.xml
        <configuration>
            <property>
                <name>yarn.nodemanager.aux-services</name>
                <value>mapreduce_shuffle</value>
            </property>
            <property>
                <name>yarn.nodemanager.aux-services.mapreduce.shuffle.class</name>
                <value>org.apache.hadoop.mapred.ShuffleHandler</value>
            </property>
            <property>
                <name>yarn.resourcemanager.address</name>
                <value>bigdata01:8032</value>
            </property>
            <property>
                <name>yarn.resourcemanager.scheduler.address</name>
                <value>bigdata01:8030</value>
            </property>
            <property>
                <name>yarn.resourcemanager.resource-tracker.address</name>
                <value>bigdata01:8031</value>
            </property>
            <property>
                <name>yarn.resourcemanager.admin.address</name>
                <value>bigdata01:8033</value>
            </property>
            <property>
                <name>yarn.resourcemanager.webapp.address</name>
                <value>bigdata01:8088</value>
            </property>
        </configuration>

01.bigdata01
	cd /usr/local
	scp -r hadoop292/ root@bigdata02:/usr/local/
	scp -r hadoop292/ root@bigdata03:/usr/local/
	
02.bigdata02
	a.配置环境变量
		vi /etc/profile
		export HADOOP_HOME=/usr/local/hadoop292
		export PATH=$HADOOP_HOME/bin:$PATH
	b.刷新环境变量
		source /etc/profile
	
03.bigdata03
	a.配置环境变量
		vi /etc/profile
		export HADOOP_HOME=/usr/local/hadoop292
		export PATH=$HADOOP_HOME/bin:$PATH
	b.刷新环境变量
		source /etc/profile
		
01.第一次使用,bigdata01格式化hdfs
	a.打开目录
		cd /usr/local/hadoop292
	b.只格式化bigdata01
		bin/hdfs namenode -format
	c.重启
		reboot
	
02.主节点启动hadoop
	a.打开目录
		cd /usr/local/hadoop292
	b.启动hadoop
		sbin/start-dfs.sh		--启动Hdfs
		sbin/start-yarn.sh		--启动MapReduce
		
03.测试是否启动成功
	a.jps 
		bigdata01:NameNode(hdfs主节点) 、 SecondaryNameNode(hdfs备份节点)
		bigdata02:DataNode(hdfs从节点)
		bigdata03:DataNode(hdfs从节点)
	b.浏览器
		http://192.168.2.128:50070/
	c.通过命令查看状态
		cd /usr/local/hadoop292
	   	bin/hdfs dfsadmin -report
	   	
04.主节点关闭hadoop 	
	a.打开目录
		cd /usr/local/hadoop292
	b.关闭hadoop
		sbin/stop-dfs.sh		--关闭Hdfs
		sbin/stop-yarn.sh		--关闭MapReduce
		
05.免密钥登陆
	1.生成密钥
		ssh-keygen -t rsa
	2.发送私钥(本机) 
		ssh-copy-id localhost
	3.发送公钥(其他计算机)
		ssh-copy-id bigdata02
	4.测试免密钥登陆 
		ssh bigdata02
	
06.防火墙
	systemctl stop firewalld
    systemctl disable firewalld
    
07.日志级别
	export HADOOP_ROOT_LOGGER=DEBUG,console

9.2.2 Zookeeper集群

01.打开目录、上传、解压、重命名
	a.打开目录
		cd /apps
	b.解压
		tar -zxvf zookeeper-3.4.6.tar.gz
	d.重命名
		mv zookeeper-3.4.6 zookeeper

02.配置zookeeper
	a.打开目录
		cd /apps/zookeeper
	b.新建目录
		mkdir data
		mkdir log
	c.打开目录
		cd /apps/zookeeper/conf
	d.配置zoo.cfg	
		mv zoo_sample.cfg zoo.cfg			--重命名
		dataDir=/apps/zookeeper/data
        dataLogDir=/apps/zookeeper/log
        server.1=bigdata01:2888:3888
        server.2=bigdata02:2888:3888
        server.3=bigdata03:2888:3888

03.将bigdata01的zookeeper克隆到bigdata02、bigdata03
	scp -r /apps/zookeeper root@bigdata02:/apps
	scp -r /apps/zookeeper root@bigdata03:/apps

04.在bigdata01、bigdata02、bigdata03的/apps/zookeeper/data/myid文件中,分别写入:1、2、3
	a.打开目录
		cd /apps/zookeeper/data
	b.新建myid
		vi myid
		1
		2
		3

05.启动zookeeper集群
	a.打开目录
		cd /apps/zookeeper/
	b.启动
		bin/zkServer.sh start		--启动
		bin/zkServer.sh status		--状态
		bin/zkServer.sh stop		--停止

06.环境变量
	vi /etc/profile
	export ZOOKEEPER_HOME=/apps/zookeeper
	export PATH=$ZOOKEEPER_HOME/bin:$PATH

07.防火墙
	systemctl stop firewalld
	systemctl disable firewalld

9.2.3 Hbase完全分布式

01.打开目录、上传、解压、重命名
	a.打开目录
		cd /apps
	b.解压
		tar -zxvf hbase-1.3.1-bin.tar.gz
	d.重命名
		mv hbase-1.3.1 hbase

02.配置文件(3个配置文件)
	a.打开目录
		cd /apps/hbase/conf
	b.hbase-site.xml
		<configuration>
            <property>
                <name>hbase.rootdir</name>
                <value>hdfs://bigdata01:9000/hbase</value>
            </property>
            <property>
                <name>hbase.cluster.distributed</name>
                <value>true</value>
            </property>
            <property>
                <name>hbase.master.port</name>
                <value>16000</value>
            </property>
            <property>
                <name>hbase.zookeeper.quorum</name>
                <value>bigdata01,bigdata02,bigdata03</value>  
            </property>
            <property>
                <name>hbase.zookeeper.property.dataDir</name>
                <value>/apps/zookeeper/data</value>  
            </property>
        </configuration>
	c.hbase-env.sh
		export HBASE_MANAGES_ZK=false					--外置zookeeper
		export JAVA_HOME=/usr/java/jdk1.8.0_231-amd64
		export HADOOP_HOME=/usr/local/hadoop292
		export HBASE_HOME=/apps/hbase/
	d.regionservers
		bigdata02
		bigdata03
		
03.将bigdata01的hbase克隆到bigdata02、bigdata03
	scp -r /apps/hbase root@bigdata02:/apps
	scp -r /apps/hbase root@bigdata03:/apps

9.2.4 Hbase环境启动

00.启动hbase之前,需要先启动zookeeper、hdfs
	a.启动hdfs
		/usr/local/hadoop292/sbin/start-dfs.sh	--启动hdfs,仅在bigdata01主节点启动一次
	b.启动zookeeper
		/apps/zookeeper/bin/zkServer.sh start	--启动zookeeper,需要在每台机器中启动,共三次
	c.启动hbase
		/apps/hbase/bin/start-hbase.sh			--启动hbase,仅在bigdata01主节点启动一次
		/apps/hbase/bin/hbase shell				--启动hbase的shell命令

9.3 Hbase数据库——Shell

9.4 Hbase数据库——Java

9.5 Hbase存储/读取爬虫数据

10 Neo4J数据库

10.1 Neo4J环境搭建

01.下载
	https://neo4j.com/

02.数据库版本
	Neo4j 3.5.14

03.登录数据库
	http://localhost:7474/
	默认用户名:neo4j
	密码:4023615

04.更多API文档
	http://neo4j.com/docs/  

10.2 Neo4J数据库——Browser

01.创建节点和关系
	CREATE (标签:标签 {属性名1:'属性值1', 属性名2:'属性值2',属性名3:'属性值3'})
	CREATE (TheMatrix:Movie {title:'The Matrix', released:1999, tagline:'Welcome to the Real World'})
	CREATE (Keanu:Person {name:'Keanu Reeves', born:1964})
	CREATE (Carrie:Person {name:'Carrie-Anne Moss', born:1967})
	CREATE (Laurence:Person {name:'Laurence Fishburne', born:1961})
	CREATE (Hugo:Person {name:'Hugo Weaving', born:1960})
	CREATE (LillyW:Person {name:'Lilly Wachowski', born:1967})
	CREATE (LanaW:Person {name:'Lana Wachowski', born:1965})
	CREATE (JoelS:Person {name:'Joel Silver', born:1952})

02.建立关系
    CREATE
    (Keanu)-[:ACTED_IN {roles:['Neo']}]->(TheMatrix),
    (Carrie)-[:ACTED_IN {roles:['Trinity']}]->(TheMatrix),
    (Laurence)-[:ACTED_IN {roles:['Morpheus']}]->(TheMatrix),
    (Hugo)-[:ACTED_IN {roles:['Agent Smith']}]->(TheMatrix),
    (LillyW)-[:DIRECTED]->(TheMatrix),
    (LanaW)-[:DIRECTED]->(TheMatrix),
    (JoelS)-[:PRODUCED]->(TheMatrix)

03.查询节点
	a.查询全部节点
		MATCH (n) RETURN n
	b.查询名为Tom Hanks的人,参演过哪些电影
		MATCH (tom:Person {name: "Tom Hanks"})-[:ACTED_IN]->(tomHanksMovies) RETURN tom,tomHanksMovies
	c.查询谁导演了Cloud Atals电影?
		MATCH (cloudAtlas {title: "Cloud Atlas"})<-[:DIRECTED]-(directors) RETURN directors.name
	d.查询名为Tom Hanks的人,然后以别名tom返回
		MATCH (tom {name: "Tom Hanks"}) RETURN tom			

04.删除全部的节点和关系
    match (n)
    optional match (n)-[r]-()
    delete n,r

05.删除
	match (p:Person),(m:Movie)					--查询某个节点
	optional match (p)-[r1]-() , (m)-[r2]-()	--路径r1:从p到全部、路径r2:从m到全部
	delete  p,m,r1,r2							--删除:人、电影、路径

10.3 Neo4J数据库——Java

01.下载和服务器版本一致的(3.5.14)neo4j的java api案例
	https://github.com/neo4j/neo4j-documentation/tree/3.5

02.引入依赖
	<dependency>
        <groupId>org.neo4j</groupId>
        <artifactId>neo4j</artifactId>
        <version>3.5.14</version>
    </dependency>

03.测试官方示例
	EmbeddedNeo4j.java 

11 SpringCloud

11.1 服务注册与发现中心:Eureka

00.场景
    micro_eureka(服务注册与发现中心) -> micro_city、micro_city2(注册的两个微服务)

01.依赖
    <dependencyManagement>
        <dependencies>
            <!-- 引入SpringBoot starter-->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-parent</artifactId>
                <version>2.1.3.RELEASE</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <!-- 引入SpringCloud-->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Greenwich.RELEASE</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

02.服务端
    a.pom.xml(依赖)
        <!--Eureka服户端-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
        </dependency>
    b.application.yml(配置)
        server:
          port: 9991
        eureka:
          client:
            register-with-eureka: false #不注册到eureka中
            fetch-registry: false #不获取eureka
            service-url:
              defaultZone: http://localhost:${server.port}/eureka/
    c.启动类(编码)
        @EnableEurekaServer//启动Eureka服务端
        @SpringBootApplication
        public class MicroEurekaApplication {
            public static void main(String[] args) {
                SpringApplication.run(MicroEurekaApplication.class, args);
            }
        }

03.客户端(micro_city)
    a.pom.xml(依赖)
        <!--Eureka服户端-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
        </dependency>
    b.application.yml(配置)
        eureka:
          client:
            service-url:
              defaultZone: http://localhost:9991/eureka/
          instance:
            instance-id: city1Id
        spring:
          application:
            name: city
    c.启动类(编码)
        @EnableEurekaClient//开启客户端
        @SpringBootApplication
        public class MicroCityApplication {
            public static void main(String[] args) {
                SpringApplication.run(MicroCityApplication.class, args);
            }
        }

04.客户端(micro_city2)
    a.pom.xml(依赖)
        <!--Eureka服户端-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
        </dependency>
    b.application.yml(配置)
        eureka:
          client:
            service-url:
              defaultZone: http://localhost:9991/eureka/
          instance:
            instance-id: city2Id
        spring:
          application:
            name: city2
    c.启动类(编码)
        @EnableEurekaClient//开启客户端
        @SpringBootApplication
        public class MicroCityApplication {
            public static void main(String[] args) {
                SpringApplication.run(MicroCityApplication.class, args);
            }
        }

11.2 服务调用:Feign-面向接口

00.场景
    micro_city3(消费者/客户端) -> CityClient接口 -> micro_city(生产者/服务端)

01.micro_city3
    a.pom.xml(依赖)
        <!--Eureka服户端-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
        </dependency>
        <!--开启服务调用:Feign-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
    b.配置(SpringCloud底层已经帮我们实现了,不需要有关于Feign的配置)
        eureka:
          client:
            service-url:
              defaultZone: http://localhost:9991/eureka/
          instance:
            instance-id: city3Id
        spring:
          application:
            name: city3
    c.启动类(@EnableDiscoveryClien、@EnableFeignClients开启两个注解)
        @EnableDiscoveryClient//开启Discovery
        @EnableFeignClients//开启Feign
        @EnableEurekaClient//开启Eureka
        @SpringBootApplication
        public class MicroCityApplication {
            public static void main(String[] args) {
                SpringApplication.run(MicroCityApplication.class, args);
            }
        }
    d.CityClient接口
        @FeignClient("city")
        public interface CityClient {

            @PostMapping("addCity")
            public Message addCity(@RequestBody City city);

            @DeleteMapping("deleteById/{id}")
            public Message deleteById(@PathVariable("id") Integer id);

            @PutMapping("updateCityById")
            public Message updateCityById(@RequestBody City city);

            @GetMapping("queryCities")
            public Message queryCities();
        }
    e.CityController3控制器
        @RestController
        public class CityController2 {
            /**
             * 二、通过CityClient接口,调用远程的micro_city的CityController层
             */
            @Autowired
            CityClient cityClient;

            @PostMapping("addCity")
            public Message addCity(@RequestBody City city){
                return cityClient.addCity(city);
            }

            @DeleteMapping("deleteById/{id}")
            public Message deleteById(@PathVariable("id") Integer id){
                return cityClient.deleteById(id);
            }

            @PutMapping("updateCityById")
            public Message updateCityById(@RequestBody City city){
                return cityClient.updateCityById(city);
            }

            @GetMapping("queryCities")
            public Message queryCities(){
                return cityClient.queryCities();
            }
        }

11.3 服务调用:Ribbon-面向URL

00.场景
    micro_city4(消费者/客户端) -> RestTemplate对象 -> micro_city(生产者/服务端)

01.micro_city4
    a.pom.xml(依赖)
        <!--Eureka客户端-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <!--开启服务调用:Ribbon-->
        <dependency>
             <groupId>org.springframework.cloud</groupId>
             <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
        </dependency>
    b.配置
        eureka:
          client:
            service-url:
              defaultZone: http://localhost:9991/eureka/
          instance:
            instance-id: city4Id
        spring:
          application:
            name: city4
    c.启动类
        相比Feign方式,无注解
    d.RestTemplate对象
        /**
         * city -> 远程对象:Spring内置RestTemplate,将该对象放入IOC容器
         */
        @Configuration
        public class ConfigBean {
            @LoadBalanced//负载均衡(可选注解)
            @Bean
            public RestTemplate getRestTemplate(){
                return new RestTemplate();
            }
        }
    e.CityController4控制器
        /**
        * 二、Ribbon方式:city -> 远程对象:Spring内置RestTemplate,将该对象放入IOC容器
        *      方式一:restTemplate.getForObject(映射地址、返回值类型、可变参数)
        *      方式二:restTemplate.getForEntity()
        */
        @Autowired
        RestTemplate restTemplate;

        @GetMapping("queryCities")
        public Message queryCities(){
            /**
            * getForObject(映射地址、返回值类型、可变参数)
            */
            return restTemplate.getForObject("http://city/queryCities", Message.class);
        }

11.4 服务熔断:Hystrix

00.场景
    micro_city5(Feign + Hystrix)

01.代码实现
    a.pom.xml(当前版本2.1.3.RELEASE与Greenwich.RELEASE,Fegin内部已支持Hystrix,不需要Hystrix依赖)
        <!--Eureka服务端-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
        </dependency>
        <!--开启服务调用:Feign-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
    b.配置(city5(开启Hystrix)-> city1)
        eureka:
          client:
            service-url:
              defaultZone: http://localhost:9991/eureka/
          instance:
            instance-id: city5Id
        spring:
          application:
            name: city5
        feign:
          hystrix:
            enabled: true #开启熔断器
    c.启动类
        @EnableDiscoveryClient//开启Discovery
        @EnableFeignClients//开启Feign
        @EnableEurekaClient//开启Eureka客户端
        @SpringBootApplication
        public class MicroCityApplication {
            public static void main(String[] args) {
                SpringApplication.run(MicroCityApplication.class, args);
            }
        }
    d.CityClient接口(开启熔断器注解)
        @FeignClient(value = "city", fallback = CityClientImpl.class)
        public interface CityClient {

            @PostMapping("addCity")
            public Message addCity(@RequestBody City city);

            @DeleteMapping("deleteById/{id}")
            public Message deleteById(@PathVariable("id") Integer id);

            @PutMapping("updateCityById")
            public Message updateCityById(@RequestBody City city); 

            @GetMapping("queryCities")
            public Message queryCities();
        }
    e.CityClientImpl(实现CityClient接口,触发本地熔断器后,返回失败消息)
        @Component
        public class CityClientImpl implements CityClient {
            @Override
            public Message addCity(City city) {
                return new Message(true, StatusCode.OK, "请求失败,触发了本地熔断器");
            }

            @Override
            public Message deleteById(Integer id) {
                return new Message(true, StatusCode.OK, "请求失败,触发了本地熔断器");
            }

            @Override
            public Message updateCityById(City city) {
                return new Message(true, StatusCode.OK, "请求失败,触发了本地熔断器");
            }

            @Override
            public Message queryCities() {
                return new Message(true, StatusCode.OK, "请求失败,触发了本地熔断器");
            }
        }

11.5 服务网关:Zuul

00.场景
    micor_zuul(动态路由)

01.代码实现
    a.pom.xml(依赖)
        <!--Eureka服务端-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
        </dependency>
        <!--Zuul服务网关-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-zuul</artifactId>
        </dependency>
        <!--SpringMVC:偏向于Controller层,需要请求映射-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    b.配置
        server:
          port: 10001
        eureka:
          client:
            service-url:
              defaultZone: http://localhost:9991/eureka/
          instance:
            instance-id: zuulId
        spring:
          application:
            name: zuul
        zuul:
          prefix: /myslayers
          ignored-services: "*"  # "city"  如果有虚拟地址,仅会屏蔽city中以前的地址,其他微服务不影响
          routes:
            city:
              path: /mycity/**
    c.启动类(编码)
        @EnableZuulProxy//开启Zuul
        @EnableEurekaServer//开启服务端
        @SpringBootApplication
        public class MicroZuulApplication {
            public static void main(String[] args) {
                SpringApplication.run(MicroZuulApplication.class, args);
            }
        }

11.6 分布式配置中心:Config

00.场景
    micro_config(分布式配置中心) -> micro_city6(使用分布式配置)

00.Github(Config约定,文件命名必须为A-B.properties/yml)
    https://github.com/wohenguaii/micro_config_rep.git
    micro-application.properties
    micro-application.yml

01.micro_city5(使用分布式配置)
    a.pom.xml(依赖)
        <!--分布式配置中心:config-server-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-config-server</artifactId>
        </dependency>
    b.application.yml(配置)
        eureka:
          instance:
            instance-id: config
        server:
          port: 10010
        spring:
          cloud:
            config:
              server:
                git:
                  uri: https://github.com/wohenguaii/micro_config_rep.git
    c.启动类(编码)
        @EnableConfigServer //开启Config
        @SpringBootApplication
        public class MicroConfigApplication {
            public static void main(String[] args) {
                SpringApplication.run(MicroConfigApplication.class, args);
            }
        }

02.micro_city6(使用分布式配置)
    a.pom.xml(依赖)
        <!--分布式配置中心:starter-config-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-config</artifactId>
        </dependency>
    b.bootstrap.yml(建议将云端配置命名为bootstrap.yml/properties,bootstrap优先级高于application)
        spring:
          cloud:
            config:
              uri: http://127.0.0.1:10010
              label: master
              name: micro             #A
              profile: application    #B
    c.编码
        使用分布式配置中心Config

12 分布式认证

12.1 单体框架JWT

12.2 SpringBoot整合JWT

00.场景
    第一次登录时(生成Token),获取token;以后登录时(校验Token),拿着token进行校验
	自定义请求头Header(key:authentication、value:myslayers-token)
    
01.micro_jwt
    a.pom.xml(依赖)
        <dependencies>
            <!--引入jjwt-->
            <dependency>
                <groupId>io.jsonwebtoken</groupId>
                <artifactId>jjwt</artifactId>
                <version>0.9.1</version>
            </dependency>
            <!--解决报错提示:注解ConfigurationProperties-->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-configuration-processor</artifactId>
                <optional>true</optional>
            </dependency>
            <!--解决报错提示:注解ConfigurationProperties-->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-security</artifactId>
            </dependency>
        </dependencies>
    b.JwtUtil(编码)
        public class JwtUtil {
            private String key = "myslayers"; //盐
            private long ttl = 18000000; //过期时间
            public String getKey() {
                return key;
            }
            public void setKey(String key) {
                this.key = key;
            }
            public long getTtl() {
                return ttl;
            }
            public void setTtl(long ttl) {
                this.ttl = ttl;
            }
            //(生成Token)服务端生成jwt的Token方法
            public String createJWT(String id, String subject, String roles) {
                return Jwts.builder().setId(id)
                        .setSubject(subject)
                        .setIssuedAt(new Date())
                        .setExpiration(new Date(System.currentTimeMillis() + ttl))   //有效时间
                        .signWith(SignatureAlgorithm.HS256, key)    //signature签名
                        .claim("roles", roles)      //设置权限
                        .compact();
            }
            //(检验Token)客户端发送jwt的Token时校验方法
            public Claims parseJWT(String token) {
                return Jwts.parser().setSigningKey(key).parseClaimsJws(token).getBody();
            }
        }

02.micro_city7(生成Token)
    a.pom.xml(依赖)
        <dependency>
            <groupId>org.myslayers</groupId>
            <artifactId>micro_jwt</artifactId>
            <version>1.0-SNAPSHOT</version>
            <scope>compile</scope>
        </dependency>
    b.ConfigBean(配置类)
        /**
         * 将JwtUtil放入该项目springboot的IOC容器
         */
        @Configuration
        public class ConfigBean {
            @Bean
            public JwtUtil getJwtUtil() {
                return new JwtUtil();
            }
        }
    c.CityController(编码:生成token)
        @RestController
        public class CityController {
            /**
             * SpringBoot整合jwt:
             * 场景:第一次登录,客户端向服务端发送请求,然后,客户端将生成的token返回给客户端
             *      第一次登录时(生成Token),获取token
             */
            @Autowired
            private JwtUtil jwtUtil;

            @PostMapping("login")
            public Message login(@RequestBody Map<String, String> login) {
                //模拟数据库操作(zs、abc、10001、theme、admin...)
                String uname = login.get("username");
                String upwd = login.get("password");
                if ("zs".equals(uname) && "abc".equals(upwd)) {
                    System.out.println("登录成功!");
                    //登录成功后,用map进行封装返回token、用户名
                    String token = jwtUtil.createJWT("1001", "theme", "admin");
                    Map<String, Object> map = new HashMap<>();
                    map.put("token", token);
                    map.put("username", uname);
                    //返回Message
                    return new Message(true, StatusCode.OK, map);
                } else {
                    System.out.println("登录失败!");
                    return new Message(false, StatusCode.ERROR, "登录失败!");
                }
            }
        }

03.micro_city7(解析Token:减少代码冗余,将解析操作放到拦截器中,一个微服务对应一个拦截器)
    a.JwtInterceptor(拦截器:解析Token)
        /**
         *  例如,删除之前,需要先通过jwt进行权限校验(用户验证)
         *  场景:假设此时,客户端postman已经将携带token的jwt传递过来,现在需要对jwt进行解析
         *       以后登录时(校验Token),拿着token进行校验
         */
        @Component
        //请求 -> 拦截器(pre) -> 校验一般为“增删改”
        public class JwtInterceptor extends HandlerInterceptorAdapter {
            @Autowired
            private JwtUtil jwtUtil;
            @Override
            public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
                System.out.println("拦截器...");
                //从header中获取authentication对应的key值(key值 = myslayers-token)
                String key = request.getHeader("authentication");
                //根据获取的key值,对token进行截取判断
                if (key != null && key.startsWith("myslayers-")) {
                    String token = key.substring(10);//myslayers-token:从第10位开始截取token
                    Claims claims = jwtUtil.parseJWT(token);//解析token
                    String roles = (String) claims.get("roles");//根据键值对,用键roles获取对应的值
                    //校验判断
                    if (roles.equals("admin")) {
                        System.out.println("管理员admin校验成功!");
                        request.setAttribute("claims", claims);//将request域中,放入claims,然后拦截器进行放行
                    }
                }
                //放行
                return true;
            }
        }
    b.JwtConfig(对拦截请求进行再一步处理:排除登录login时,需要进行校验) 
        /**
         * 对拦截请求进行再一步处理:排除登录login时,需要进行校验
         */
        @Configuration
        public class JwtConfig extends WebMvcConfigurationSupport {
            @Autowired
            JwtInterceptor jwtInterceptor;
            @Override
            protected void addInterceptors(InterceptorRegistry registry) {
                registry.addInterceptor(jwtInterceptor)
                        .addPathPatterns("/**")                     //拦截全部请求
                        .excludePathPatterns("/**/login");          //排除login登录请求
            }
        }
    c.CityController(编码:利用拦截器处理不同的请求)
        @RestController
        public class CityController {
            @Autowired
            CityService cityService;
            @Autowired
            HttpServletRequest request;
            private Message checkToken() {
                //如果拦截器判断权限足够,则会在request中放入一个claims参数,否则,则没有claims参数
                Claims claims = (Claims) request.getAttribute("claims");
                //判断是否存在claims参数
                if (claims == null) {
                    return new Message(false, StatusCode.ERROR, "权限不足!");
                }
                return new Message(false, StatusCode.ERROR, "获得权限!");
            }

            @PostMapping(value = "addCity7")
            public Message addCity7(@RequestBody City city){
                //校验
                checkToken();
                boolean result = cityService.addCity(city);
                return new Message(true, StatusCode.OK, result);
            }

            @DeleteMapping("deleteById7/{id}")
            public Message deleteById7(@PathVariable("id") Integer id){
                //校验
                checkToken();
                //校验(admin用户)成功!进行删除操作
                boolean result = cityService.deleteById(id);
                return new Message(true, StatusCode.OK, result);
            }

            @PutMapping("updateCityById7")
            public Message updateCityById7(@RequestBody City city){
                //校验
                checkToken();
                boolean result = cityService.updateCityById(city);
                return new Message(true, StatusCode.OK, result);
            }

            @GetMapping("queryCities7")
            public Message queryCities7(){
                List<City> cities = cityService.queryCities();
                return new Message(true, StatusCode.OK, cities);
            }
        }

13 分布式

13.1 CAP原则

01.CAP原则又称CAP定理,指的是在一个分布式系统中,存在Consistency(一致性)、Availability(可用性)、Partition tolerance(分区容错性),三者不可同时保证,最多只能保证其中的两者。   
    一致性(C):在分布式系统中的所有数据备份,在同一时刻都是同样的值(所有的节点无论何时访问都能拿到最新的值)
    可用性(A):系统中非故障节点收到的每个请求都必须得到响应(比如我们之前使用的服务降级和熔断,其实就是一种维持可用性的措施,虽然服务返回的是没有什么意义的数据,但是不至于用户的请求会被服务器忽略)
    分区容错性(P):一个分布式系统里面,节点之间组成的网络本来应该是连通的,然而可能因为一些故障(比如网络丢包等,这是很难避免的),使得有些节点之间不连通了,整个网络就分成了几块区域,数据就散布在了这些不连通的区域中(这样就可能出现某些被分区节点存放的数据访问失败,我们需要来容忍这些不可靠的情况)

02.只能存在以下三种方案
    a.AC(可用性+一致性)
        要同时保证可用性和一致性,代表着某个节点数据更新之后,需要立即将结果通知给其他节点,并且要尽可能的快,
        这样才能及时响应保证可用性,这就对网络的稳定性要求非常高,但是实际情况下,网络很容易出现丢包等情况,
        并不是一个可靠的传输,如果需要避免这种问题,就只能将节点全部放在一起,但是这显然违背了分布式系统的概念,
        所以对于我们的分布式系统来说,很难接受。
    b.CP(一致性+分区容错性)
        为了保证一致性,那么就得将某个节点的最新数据发送给其他节点,并且需要等到所有节点都得到数据才能进行响应,
        同时有了分区容错性,那么代表我们可以容忍网络的不可靠问题,所以就算网络出现卡顿,
        那么也必须等待所有节点完成数据同步,才能进行响应,因此就会导致服务在一段时间内完全失效,
        所以可用性是无法得到保证的。
    c.AP(可用性+分区容错性)
        既然CP可能会导致一段时间内服务得不到任何响应,那么要保证可用性,就只能放弃节点之间数据的高度统一,
        也就是说可以在数据不统一的情况下,进行响应,因此就无法保证一致性了。
        虽然这样会导致拿不到最新的数据,但是只要数据同步操作在后台继续运行,
        一定能够在某一时刻完成所有节点数据的同步,那么就能实现最终一致性,所以AP实际上是最能接受的一种方案。
        比如我们实现的Eureka集群,它使用的就是AP方案,Eureka各个节点都是平等的,少数节点挂掉不会影响正常节点的工作,
        剩余的节点依然可以提供注册和查询服务。而Eureka客户端在向某个Eureka服务端注册时如果发现连接失败,
        则会自动切换至其他节点。只要有一台Eureka服务器正常运行,那么就能保证服务可用(A),
        只不过查询到的信息可能不是最新的(C)

03.并发VS并行
    a.并发是同时处理很多的事情,同时处理一下四个并发步骤:
         1. 把书装到车上;
         2. 把推车运到火炉旁;
         3. 把书卸到火炉里;
         4. 运回空推车。
    b.说明
        并行是同时做很多的事情,分为两组同时执行同一任务

01.分布式
    基于分布式:大数据、人工智能、区块链、边缘计算、微服务
    核心:"拆"
    微服务与分布式:分布式->拆了就行(横向、纵向)
                              微服务->纵向拆分,最小化拆分
    横向拆分:jsp/servlet->service -dap
    纵向拆分:按照业务逻辑拆分,电商:用户、支付、购物…

02.CPA理论:任何一个分布式系统都必须重点考虑的原则
    内容:在任何分布式系统中,C、A、P不能够同时共存,只能存在两个;一般而言,至少要保证P可行,因为分布式中经常会出现“弱网环境”,因此就需要在C和A之间二选一
    C:强一致性,子节点中的数据时刻需要保持一致,如果满足一致性,则数据会造成回滚,不会提交,则数据不可用
    A:可用性,整体能够使用(在合理的时间范围内,系统能够提供正常的服务)
    P:分区容错性,允许部分失败(当分布式系统中的一个或多个节点发生网络故障(网络分区),从而脱离整个系统的网络环境时,系统仍然能够提供可靠的服务)

03.BASE理论:Basically Available
    内容:弥补CAP的不足,尽最大努力近似地实现CAP三者共同实现
    核心:用“最终一致性”代替“强一致性”,首选满足A\P,因此不能满足C,但是可以用最终一致性代替C
    注意:软状态:多个节点时,允许中间某个时刻数据不一致
         强一致性:多个节点时,时时刻刻保持一致
         最终一致性:多个节点时,最后一致就行

04.高并发原则
    场景:双十一、春晚发红包、12306购票
    解决方案:①垂直扩展:通过软件技术或者升级硬件,来提高单机的能力
    ②水平扩展:通过增加服务器的节点个数,来横向扩充系统的性能。常用:集群(失败迁移、负载均衡)/分布式

05.幂等性原则
    内容:幂等性原则是对调用服务次数的一种限制,即无论对某个服务提供的接口调用多次或是一次,其结果都是相同的。
    场景:防止重复购买
    实现方式:①算法
             ②去重表:a.每次操作在第一次执行时,会生成一个全局唯一D,如订单D
                     b.在"去重表"中查询"1"中的1D是否已存在
                     c.如果已存在,直接返回结果;如果不存在,再执行核心操作(如支付),并将”1"中的D存入"去重表"中,最后返回结果

06.数据共享原则
    ①Session Replication:在客户端第一次发出请求后,
    处理该请求的服务端就会创建一个与之对应的Session?对象,用于保存客户端的状态信息,
    之后为了让其他服务器也能保存一份此Session对象,就需要将此Session对象在各个服务端节点之间进行同步
    优点:数据共享后,客户端只要向该集群中的任何一台机器成功发送过一次请求,就能够在全部的集群节点进行访问
    
    ②Session Stidky:Session Stidky:通过Nginx等负载均衡工具对各个用户进行标记(例如对Cookie标记),
    使每个用户在经过负载工具后都请求固定的服务节点
    优点:固定的请求节点
    缺点:不支持高可用,每份数据都需要单独处理

    ③独立Session服务器:将系统中所有的Session对象都存放到一个独立的Session服务中,
    之后各个应用服务器再分别从这个Session服务中获取需要的Session对象
    步骤:①查询redis中的session是否存在
    ②如果存在,则登陆!如果不存在,则不登陆!
    
07.无状态原则
    内容:将“状态建立存储,从而实现应用服务的”无状态”
   优点:①在“无状态“的服务中,单个服务的宕机、重启等都不会影响到集群中的其他服务,并且很容易对应用服务进行横向扩展
    ②另一方面,将带有数据的服务设置为"有状态”,并进行集群的"集中部署”(如ySQL集群),可以降低集群内部数据同步带来的延迟 

13.2 缓存问题

01.缓存击穿:某一个热点数据过期,造成大量用户请求直奔DB的现象
    解决:1.监控线程,假设redis缓存中数据将要过期,及时给它更新过期时间
         2.提前设置好时间,保证热点数据在高峰期不过期,调大redis中的过期时间
    场景:双十一秒杀iphone12,通常在秒杀前,提前将iPhone12数据放入缓存中
         ①缓存正常:用户直接访问缓存,可以减轻数据库压力
         ②缓存过期:redis缓存中iPhone12突然过期,会造成大量用户请求直奔DB

02.缓存雪崩:大量缓存全部失效
    场景一:大量缓存设置了相同的过期时间,原因:代码没写好
    场景二:缓存服务器故障,宕机状态

03.缓存穿透:防止恶意攻击。
    一般而言,我们不会缓存一些无意义的数据,但是如果非要利用一些无意义的数据反复发起请求,
    会形成一种恶意攻击,从而使得大量数据库资源由此造成浪费
    场景:用户发出多条缓存中不存在的"恶意数据”,跳过缓存,大量请求访问DB
    解决:将无意义的数据也进行缓存,并且将过期时间设置的相对短一些

    以上的本质都是"缓存失效”,通用的解决方案:二级缓存(分布式缓存)
    一级缓存:一般是指本地缓存
    二级缓存:分布式缓存

01.什么是redis击穿?
    用户请求透过redis去请求mysql服务器,导致mysql压力过载。但一个web服务里,极容易出现瓶颈的就是mysql,所以才让redis去分担mysql 的压力,所以这种问题是万万要避免的
    解决方法:
        从缓存取不到的数据,在数据库中也没有取到,这时也可以将key-value对写为key-null,缓存有效时间可以设置短点,如30秒(设置太长会导致正常情况也没法使用)。这样可以防止攻击用户反复用同一个id暴力攻击
        接口层增加校验,如用户鉴权校验,id做基础校验,id<=0的直接拦截;
        采用布隆过滤器,将所有可能存在的数据哈希到一个足够大的 bitmap 中,一个一定不存在的数据会被这个 bitmap 拦截掉,从而避免了对底层存储系统的查询压力

02.什么是redis雪崩?
    redis服务由于负载过大而宕机,导致mysql的负载过大也宕机,最终整个系统瘫痪
    解决方法:
        redis集群,将原来一个人干的工作,分发给多个人干
        缓存预热(关闭外网访问,先开启mysql,通过预热脚本将热点数据写入缓存中,启动缓存。开启外网服务)
        数据不要设置相同的生存时间,不然过期时,redis压力会大

03.什么是redis雪崩?
    高并发下,由于一个key失效,而导致多个线程去mysql查同一业务数据并存到redis(并发下,存了多份数据),而一段时间后,多份数据同时失效。导致压力骤增
    分级缓存(缓存两份数据,第二份数据生存时间长一点作为备份,第一份数据用于被请求命中,如果第二份数据被命中说明第一份数据已经过期,要去mysql请求数据重新缓存两份数据)
    计划任务(假如数据生存时间为30分钟,计划任务就20分钟执行一次更新缓存数据)

13.3 一致性hash

13.4 使用zookeeper实现分布式锁

13.5 缓存一致性

00.如何保证缓存与数据库的双写一致性?
    四种同步策略:
    想要保证缓存与数据库的双写一致,一共有4种方式,即4种同步策略:
        先更新缓存,再更新数据库;
        先更新数据库,再更新缓存;
        先删除缓存,再更新数据库;
        先更新数据库,再删除缓存。
    应该先操作数据库还是先操作缓存?
        先更新数据库、再删除缓存是影响更小的方案

13.5.1 读和写的执行顺序

13.5.2 当DB更新后,立即更新缓存

13.5.3 当DB更新后,立即删除缓存

13.6 使用2PC实现分布式事务

13.7 分布式认证&分布式授权