├── .gitignore ├── README.md ├── catalog ├── java.md ├── redis.md ├── spring-cloud.md ├── spring.md ├── springboot.md ├── 架构之路.md └── 面试篇.md ├── jdk ├── JVM相关 │ └── JDK7和JDK8的内存区域划分.md ├── NIO │ ├── NIO │ │ ├── NIO之 通道之间的数据传输.md │ │ ├── NIO之Channel.md │ │ ├── NIO之DatagramChannel.md │ │ ├── NIO之FileChannel.md │ │ ├── NIO之Pipe.md │ │ ├── NIO之Scatter和Gather.md │ │ ├── NIO之ServerSocketChannel.md │ │ ├── NIO之SocketChannel.md │ │ ├── NIO之buffer.md │ │ └── NIO之selector.md │ └── 三种IO的总结.md ├── collection │ ├── ArrayList.md │ ├── HashMap.md │ ├── LinkedHashMap.md │ ├── LinkedList.md │ ├── Spliterator.md │ └── Vector.md ├── jdk8 │ ├── Lambda.md │ ├── Lambda在集合中的使用.md │ ├── 使用Optional.md │ ├── 并行流.md │ ├── 日期类 │ │ ├── 操作解析格式化日期.md │ │ └── 日期类.md │ └── 流 │ │ ├── CompletableFuture.md │ │ ├── 分区.md │ │ ├── 分组.md │ │ ├── 收集器.md │ │ ├── 数值流.md │ │ └── 流创建的几种方式.md └── thread │ ├── CAS.md │ ├── CountDownLatch源码解析.md │ ├── CyclicBarrier源码解析.md │ ├── Exchanger.md │ ├── ReentrantLock.md │ ├── ReentrantLock源码分析.md │ ├── Semaphore.md │ ├── Thead源码分析 .md │ ├── ThreadLocal源码分析.md │ ├── happens-bofore.md │ ├── java内存模型重排序.md │ ├── java锁的升级.md │ ├── unSafe.md │ ├── volatile 的实现原理.md │ ├── 多线程的创建方式.md │ ├── 深入分析 CAS.md │ ├── 深入分析 synchronized 的实现原理.md │ ├── 深入理解ConcurrentSkipListMap.md │ ├── 理解AbstractQueuedSynchronized.md │ └── 读写锁-ReentrantReadWriteLock.md ├── linux └── VMware如何配置静态IP.md ├── markdown └── markdown--table样式.md ├── mysql ├── mysql--InnoDB.md ├── mysql--MyISAM.md ├── mysql--MySQL慢日志.md ├── mysql--explain.md ├── mysql--mysql cluster.md ├── mysql--未解决的问题.md ├── mysql--索引类型.md ├── mysql中涉及的技术.md ├── mysql中的搜索引擎.md └── 悲观锁和乐观锁的实现.md ├── redis ├── Bitmpas.md ├── HyperLogLog.md ├── PipeLine.md ├── RDB和AOF.md ├── redis--Hash.md ├── redis--list.md ├── redis--string.md ├── redis--zset.md ├── redis--发布订阅.md ├── redis-benchmark详解.md ├── redis-cli详解.md ├── redis-server详解.md ├── redis-set.md ├── redis全局命令.md ├── 主从复制 │ ├── 主从复制中读写分离带来的问题.md │ ├── 主从复制的原理.md │ ├── 主从复制的拓扑结构.md │ └── 如何配置主从.md ├── 事务和Lua.md ├── 哨兵 │ ├── redis哨兵配置说明.md │ ├── sentinel命令.md │ ├── 哨兵原理.md │ └── 如何配置redis哨兵.md ├── 慢查询分析.md ├── 缓存设计 │ └── 缓存设计.md ├── 阻塞 │ └── 发现阻塞.md └── 集群 │ └── redis cluster.md ├── spring-cloud ├── Feign │ ├── Feign传递复杂对象.md │ ├── Feign入门教程.md │ ├── Feign功能.md │ ├── Feign和Hystrix结合使用.md │ ├── Feign实现负载均衡.md │ └── Feign的介绍.md ├── Hystrix │ ├── Actuator.md │ ├── HystrixCommand和HystrixObservableCommand.md │ ├── Hystrix入门小例子.md │ ├── Hystrix合并请求.md │ ├── Hystrix异常处理机制.md │ ├── Hystrix概述.md │ ├── Hystrix线程传递和并发策略.md │ ├── Hystrix缓存处理.md │ ├── Hystrix配置文件说明.md │ └── Turbine聚合Hystrix.md ├── config │ ├── config入门示例.md │ ├── git配置详解.md │ ├── 使用多个git库.md │ └── 基于db的config.md ├── consul │ ├── consul具体功能.md │ ├── consul实现服务的注册和发现.md │ ├── consul概念介绍.md │ ├── consul的安装.md │ └── consul的配置中心.md ├── eureka │ ├── eureka rest api.md │ ├── eureka入门教程.md │ └── eureka配置项.md ├── gateway │ ├── gateWay内置的Filter.md │ ├── gateWay概念.md │ ├── gatewayFileter和GlobalFilter.md │ ├── gateway基于服务发现的路由规则.md │ └── gateway路由断言.md ├── ribbon │ ├── RestTemplate对象.md │ ├── Ribbon入门教程.md │ ├── Ribbon实践.md │ └── 使用ribbon调用第三方HTTPS接口.md ├── swagger │ ├── swagger和zuul的结合.md │ └── swagger常用注解.md ├── zuul │ ├── Zuul-Filter责任链.md │ ├── Zuul入门教程.md │ ├── Zuul功能.md │ ├── Zuul动态路由.md │ ├── Zuul权限.md │ ├── Zuul灰度发布.md │ ├── Zuul配置.md │ └── Zuul限流.md ├── 备注.md └── 链路监控 │ ├── Sleuth基本术语.md │ ├── Sleuth基本用法.md │ ├── skywalking介绍.md │ └── zipkin的基本使用.md ├── spring ├── Spring注册解析的bean.md ├── Spring自定义标签解析.md ├── Spring默认标签中的自定义标签解析.md ├── Spring默认标签解析.md ├── spring核心类.md ├── 深度解析:spring是如何实现从配置文件到注入bean.md └── 配置文件的封装.md ├── springboot └── springboot如何实现email发送.md ├── web ├── HTTP和HTTPS.md └── 对称加密.md ├── 数据结构 ├── B+树.md ├── B树.md ├── 二叉搜索树.md ├── 排序 │ ├── 八种排序算法.md │ └── 扩展:三种交换数字的方式.md └── 红黑树.md ├── 架构之路 ├── 代码质量管理平台SonarQube │ └── 代码质量管理平台SonarQube.md └── 技术相关 │ └── Redis--防击穿和雪崩.md └── 面试题 ├── Spring ├── SpringAOP.md ├── SpringBean循环创建.md ├── SpringIoc.md ├── Spring事务.md └── 几种代理的实现方式.md └── Zookeeper ├── Zookeeper命令.md └── Zookeeper面试题.md /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | .gitignore 3 | .idea/ 4 | git/ 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # markdown 2 | >关于自己的一些学习文档和学习心得都放在这里啦!!! 3 | 4 | # mysql 5 | [mysql--InnoDB](https://github.com/wuxiaobo000111/markdown/blob/master/mysql/mysql--InnoDB.md "mysql--InnoDB")
6 | 7 | [mysql--MyISAM](https://github.com/wuxiaobo000111/markdown/blob/master/mysql/mysql--MyISAM.md "mysql--MyISAM")
8 | 9 | [mysql--索引类型](https://github.com/wuxiaobo000111/markdown/blob/master/mysql/mysql--%E7%B4%A2%E5%BC%95%E7%B1%BB%E5%9E%8B.md "mysql--索引类型")
10 | 11 | [mysql--搜索引擎](https://github.com/wuxiaobo000111/markdown/blob/master/mysql/mysql%E4%B8%AD%E7%9A%84%E6%90%9C%E7%B4%A2%E5%BC%95%E6%93%8E.md "mysql--搜索引擎")
12 | 13 | [悲观锁和乐观锁的实现](https://github.com/wuxiaobo000111/markdown/blob/master/mysql/%E6%82%B2%E8%A7%82%E9%94%81%E5%92%8C%E4%B9%90%E8%A7%82%E9%94%81%E7%9A%84%E5%AE%9E%E7%8E%B0.md "悲观锁和乐观锁的实现")
14 | 15 | [mysql--explain](https://github.com/wuxiaobo000111/markdown/blob/master/mysql/mysql--explain.md "悲观锁和乐观锁的实现")
16 | 17 | [MySQL Cluster](https://github.com/wuxiaobo000111/markdown/blob/master/mysql/mysql--mysql%20cluster.md "MySQL Cluster")
18 | 19 | [mysql--慢日志](https://github.com/wuxiaobo000111/markdown/blob/master/mysql/mysql--MySQL%E6%85%A2%E6%97%A5%E5%BF%97.md "慢日志") 20 | 21 | # java 22 | [java](https://github.com/wuxiaobo000111/markdown/blob/master/catalog/java.md "java") 23 | 24 | 25 | 26 | # linux 27 | [VMware如何配置静态IP](https://github.com/wuxiaobo000111/markdown/blob/master/linux/VMware%E5%A6%82%E4%BD%95%E9%85%8D%E7%BD%AE%E9%9D%99%E6%80%81IP.md "VMware如何配置静态IP") 28 | 29 | # 数据结构 30 | 31 | 32 | [八大排序算法](https://github.com/wuxiaobo000111/markdown/blob/master/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84/%E6%8E%92%E5%BA%8F/%E5%85%AB%E7%A7%8D%E6%8E%92%E5%BA%8F%E7%AE%97%E6%B3%95.md "八大排序算法") 33 | 34 | [三种交换数字的方式](https://github.com/wuxiaobo000111/markdown/blob/master/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84/%E6%8E%92%E5%BA%8F/%E6%89%A9%E5%B1%95%EF%BC%9A%E4%B8%89%E7%A7%8D%E4%BA%A4%E6%8D%A2%E6%95%B0%E5%AD%97%E7%9A%84%E6%96%B9%E5%BC%8F.md "三种交换数字的方式") 35 | 36 | 37 | [B-树](https://github.com/wuxiaobo000111/markdown/blob/master/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84/B%E6%A0%91.md "B-树") 38 | 39 | [B+树](https://github.com/wuxiaobo000111/markdown/blob/master/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84/B%2B%E6%A0%91.md "B+树") 40 | 41 | 42 | [二叉搜索树](https://github.com/wuxiaobo000111/markdown/blob/master/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84/%E4%BA%8C%E5%8F%89%E6%90%9C%E7%B4%A2%E6%A0%91.md "二叉搜索树") 43 | 44 | 45 | 46 | # WEB 47 | 48 | [HTTP和HTTPS](https://github.com/wuxiaobo000111/markdown/blob/master/web/HTTP%E5%92%8CHTTPS.md "HTTP和HTTPS") 49 | 50 | 51 | [对称加密](https://github.com/wuxiaobo000111/markdown/blob/master/web/%E5%AF%B9%E7%A7%B0%E5%8A%A0%E5%AF%86.md "对称加密") 52 | 53 | 54 | # redis 55 | 56 | 57 | [redis相关]( 58 | https://github.com/wuxiaobo000111/markdown/blob/master/catalog/redis.md "redis相关") 59 | 60 | 61 | # spring boot 62 | 63 | [spring boot技术相关]( 64 | https://github.com/wuxiaobo000111/Java--apollo/blob/master/catalog/springboot.md "spring boot技术相关") 65 | 66 | 67 | # Spring-cloud 68 | 69 | [spring-cloud 组件]( 70 | https://github.com/wuxiaobo000111/markdown/blob/master/catalog/spring-cloud.md "spring-cloud 组件") 71 | 72 | 73 | 74 | # 架构之路 75 | 76 | [架构之路]( 77 | https://github.com/wuxiaobo000111/Java--apollo/blob/master/catalog/%E6%9E%B6%E6%9E%84%E4%B9%8B%E8%B7%AF.md "架构之路") 78 | 79 | 80 | # 面试篇 81 | 82 | [面试篇]( 83 | https://github.com/wuxiaobo000111/Java--apollo/blob/master/catalog/%E9%9D%A2%E8%AF%95%E7%AF%87.md "面试篇") -------------------------------------------------------------------------------- /catalog/redis.md: -------------------------------------------------------------------------------- 1 | # redis 2 | 3 | ## 基础 4 | [redis--hash](https://github.com/wuxiaobo000111/markdown/blob/master/redis/redis--Hash.md "redis--hash") 5 | 6 | [redis--list](https://github.com/wuxiaobo000111/markdown/blob/master/redis/redis--list.md "redis --list") 7 | 8 | [redis--string](https://github.com/wuxiaobo000111/markdown/blob/master/redis/redis--string.md 9 | "redis--string") 10 | 11 | [redis--zset](https://github.com/wuxiaobo000111/markdown/blob/master/redis/redis--zset.md "redis--zset") 12 | 13 | [redis--set](https://github.com/wuxiaobo000111/markdown/blob/master/redis/redis-set.md "redis--set") 14 | 15 | [redis--全局命令](https://github.com/wuxiaobo000111/markdown/blob/master/redis/redis%E5%85%A8%E5%B1%80%E5%91%BD%E4%BB%A4.md "redis--全局命令") 16 | 17 | [redis--Bitmaps](https://github.com/wuxiaobo000111/markdown/blob/master/redis/Bitmpas.md "redis--Bitmaps") 18 | 19 | 20 | [redis--全局命令](https://github.com/wuxiaobo000111/markdown/blob/master/redis/redis%E5%85%A8%E5%B1%80%E5%91%BD%E4%BB%A4.md "redis--全局命令") 21 | 22 | 23 | [redis--pipeline](https://github.com/wuxiaobo000111/markdown/blob/master/redis/PipeLine.md "redis--pipeline") 24 | 25 | 26 | [redis--redis-benchmark详解](https://github.com/wuxiaobo000111/markdown/blob/master/redis/redis-benchmark%E8%AF%A6%E8%A7%A3.md "redis--redis-benchmark详解") 27 | 28 | 29 | [redis--事务和Lua](https://github.com/wuxiaobo000111/markdown/blob/master/redis/%E4%BA%8B%E5%8A%A1%E5%92%8CLua.md "redis--事务和Lua") 30 | 31 | 32 | [redis--慢查询分析]( 33 | https://github.com/wuxiaobo000111/markdown/blob/master/redis/%E6%85%A2%E6%9F%A5%E8%AF%A2%E5%88%86%E6%9E%90.md "redis--全局命令") 34 | 35 | 36 | 37 | [redis--redis-cli详解]( 38 | https://github.com/wuxiaobo000111/markdown/blob/master/redis/redis-cli%E8%AF%A6%E8%A7%A3.md 39 | "redis--redis-cli详解") 40 | 41 | [redis--HyperLogLog](https://github.com/wuxiaobo000111/markdown/blob/master/redis/HyperLogLog.md "redis--HyperLogLog") 42 | 43 | 44 | [redis--发布订阅](https://github.com/wuxiaobo000111/markdown/blob/master/redis/redis--%E5%8F%91%E5%B8%83%E8%AE%A2%E9%98%85.md "redis--发布定于") 45 | 46 | 47 | [redis--RDB和AOF](https://github.com/wuxiaobo000111/markdown/blob/master/redis/RDB%E5%92%8CAOF.md "redis--RDB和AOF") 48 | 49 | ## 主从复制 50 | 51 | [redis--如何配置主从](https://github.com/wuxiaobo000111/markdown/blob/master/redis/%E4%B8%BB%E4%BB%8E%E5%A4%8D%E5%88%B6/%E5%A6%82%E4%BD%95%E9%85%8D%E7%BD%AE%E4%B8%BB%E4%BB%8E.md "redis--如何配置主从") 52 | 53 | [redis--主从复制拓扑结构](https://github.com/wuxiaobo000111/markdown/blob/master/redis/%E4%B8%BB%E4%BB%8E%E5%A4%8D%E5%88%B6/%E4%B8%BB%E4%BB%8E%E5%A4%8D%E5%88%B6%E7%9A%84%E6%8B%93%E6%89%91%E7%BB%93%E6%9E%84.md "redis--主从复制拓扑结构") 54 | 55 | [redis--主从复制原理](https://github.com/wuxiaobo000111/markdown/blob/master/redis/%E4%B8%BB%E4%BB%8E%E5%A4%8D%E5%88%B6/%E4%B8%BB%E4%BB%8E%E5%A4%8D%E5%88%B6%E7%9A%84%E5%8E%9F%E7%90%86.md "redis--主从复制原理") 56 | 57 | [redis--主从复制带来的问题](https://github.com/wuxiaobo000111/markdown/blob/master/redis/%E4%B8%BB%E4%BB%8E%E5%A4%8D%E5%88%B6/%E4%B8%BB%E4%BB%8E%E5%A4%8D%E5%88%B6%E4%B8%AD%E8%AF%BB%E5%86%99%E5%88%86%E7%A6%BB%E5%B8%A6%E6%9D%A5%E7%9A%84%E9%97%AE%E9%A2%98.md "redis--RDB和AOF") 58 | 59 | 60 | ## 阻塞 61 | 62 | 63 | [redis--阻塞]( 64 | https://github.com/wuxiaobo000111/markdown/blob/master/redis/%E9%98%BB%E5%A1%9E/%E5%8F%91%E7%8E%B0%E9%98%BB%E5%A1%9E.md "redis--阻塞") 65 | 66 | # 哨兵 67 | 68 | 69 | [redis--如何配置redis哨兵]( 70 | https://github.com/wuxiaobo000111/markdown/blob/master/redis/%E5%93%A8%E5%85%B5/%E5%A6%82%E4%BD%95%E9%85%8D%E7%BD%AEredis%E5%93%A8%E5%85%B5.md "redis--如何配置redis哨兵.") 71 | 72 | 73 | [redis--哨兵配置说明]( 74 | https://github.com/wuxiaobo000111/markdown/blob/master/redis/%E5%93%A8%E5%85%B5/redis%E5%93%A8%E5%85%B5%E9%85%8D%E7%BD%AE%E8%AF%B4%E6%98%8E.md "redis--哨兵配置说明") 75 | 76 | 77 | [redis--sentinel命令]( 78 | https://github.com/wuxiaobo000111/markdown/blob/master/redis/%E5%93%A8%E5%85%B5/sentinel%E5%91%BD%E4%BB%A4.md "redis--sentinel命令") 79 | 80 | 81 | [redis--sentinel原理]( 82 | https://github.com/wuxiaobo000111/markdown/blob/master/redis/%E5%93%A8%E5%85%B5/%E5%93%A8%E5%85%B5%E5%8E%9F%E7%90%86.md "redis--sentinel原理") 83 | 84 | # 集群 85 | 86 | 87 | [redis--集群搭建]( 88 | https://github.com/wuxiaobo000111/markdown/blob/master/redis/%E9%9B%86%E7%BE%A4/redis%20cluster.md "redis--集群搭建") 89 | 90 | # 缓存设计 91 | 92 | 93 | [redis--缓存设计]( 94 | https://github.com/wuxiaobo000111/markdown/blob/master/redis/%E7%BC%93%E5%AD%98%E8%AE%BE%E8%AE%A1/%E7%BC%93%E5%AD%98%E8%AE%BE%E8%AE%A1.md "redis--缓存设计") -------------------------------------------------------------------------------- /catalog/spring.md: -------------------------------------------------------------------------------- 1 | # spring 2 | 3 | 4 | # 源码解析篇 5 | 6 | [Spring源码解析篇--配置文件的封装](https://github.com/wuxiaobo000111/markdown/blob/master/spring/%E9%85%8D%E7%BD%AE%E6%96%87%E4%BB%B6%E7%9A%84%E5%B0%81%E8%A3%85.md "Spring源码解析篇--配置文件的封装") ClassPathResource类的继承关系。 7 | 8 | [Spring源码解析篇-spring核心类](https://github.com/wuxiaobo000111/markdown/blob/master/spring/spring%E6%A0%B8%E5%BF%83%E7%B1%BB.md "Spring源码解析篇--spring核心类") spring核心类的简单介绍 9 | 10 | [Spring源码解析篇--Spring注册解析的bean](https://github.com/wuxiaobo000111/markdown/blob/master/spring/Spring%E6%B3%A8%E5%86%8C%E8%A7%A3%E6%9E%90%E7%9A%84bean.md "Spring源码解析篇--Spring注册解析的bean") Spring注册解析的bean 11 | 12 | [Spring源码解析篇--Spring默认标签解析](https://github.com/wuxiaobo000111/markdown/blob/master/spring/Spring%E9%BB%98%E8%AE%A4%E6%A0%87%E7%AD%BE%E8%A7%A3%E6%9E%90.md "Spring源码解析篇--Spring默认标签解析") Spring默认标签解析 13 | 14 | 15 | [Spring源码解析篇--Spring默认标签中的自定义标签解析](https://github.com/wuxiaobo000111/markdown/blob/master/spring/Spring%E9%BB%98%E8%AE%A4%E6%A0%87%E7%AD%BE%E4%B8%AD%E7%9A%84%E8%87%AA%E5%AE%9A%E4%B9%89%E6%A0%87%E7%AD%BE%E8%A7%A3%E6%9E%90.md "Spring源码解析篇--Spring默认标签中的自定义标签解析") Spring默认标签中的自定义标签解析 16 | 17 | 18 | 19 | 20 | [Spring源码解析篇--Spring自定义标签解析](https://github.com/wuxiaobo000111/markdown/blob/master/spring/Spring%E8%87%AA%E5%AE%9A%E4%B9%89%E6%A0%87%E7%AD%BE%E8%A7%A3%E6%9E%90.md "Spring源码解析篇--Spring自定义标签解析") Spring自定义标签解析 21 | 22 | -------------------------------------------------------------------------------- /catalog/springboot.md: -------------------------------------------------------------------------------- 1 | # springboot 2 | 3 | 4 | [springboot如何实现email发送](https://github.com/wuxiaobo000111/Java--apollo/blob/master/springboot/springboot%E5%A6%82%E4%BD%95%E5%AE%9E%E7%8E%B0email%E5%8F%91%E9%80%81.md "springboot如何实现email发送]") -------------------------------------------------------------------------------- /catalog/架构之路.md: -------------------------------------------------------------------------------- 1 | 2 | # 架构之路 3 | 4 | ## 技术相关 5 | [Redis--防击穿和雪崩](https://github.com/wuxiaobo000111/Java--apollo/blob/master/%E6%9E%B6%E6%9E%84%E4%B9%8B%E8%B7%AF/%E6%8A%80%E6%9C%AF%E7%9B%B8%E5%85%B3/Redis--%E9%98%B2%E5%87%BB%E7%A9%BF%E5%92%8C%E9%9B%AA%E5%B4%A9.md "Redis--防击穿和雪崩") 6 | 7 | ## 代码质量管理平台 8 | 9 | [代码质量管理平台SonarQube](https://github.com/wuxiaobo000111/Java--apollo/blob/master/%E6%9E%B6%E6%9E%84%E4%B9%8B%E8%B7%AF/%E4%BB%A3%E7%A0%81%E8%B4%A8%E9%87%8F%E7%AE%A1%E7%90%86%E5%B9%B3%E5%8F%B0SonarQube/%E4%BB%A3%E7%A0%81%E8%B4%A8%E9%87%8F%E7%AE%A1%E7%90%86%E5%B9%B3%E5%8F%B0SonarQube.md "代码质量管理平台SonarQube") -------------------------------------------------------------------------------- /catalog/面试篇.md: -------------------------------------------------------------------------------- 1 | # 面试篇 2 | 3 | ## Spring 4 | 5 | [Spring--SpringIoc](https://github.com/wuxiaobo000111/Java--apollo/blob/master/%E9%9D%A2%E8%AF%95%E9%A2%98/Spring/SpringIoc.md "Spring--SpringIoc") 6 | 7 | 8 | [Spring--SpringBean循环创建](https://github.com/wuxiaobo000111/Java--apollo/blob/master/%E9%9D%A2%E8%AF%95%E9%A2%98/Spring/SpringBean%E5%BE%AA%E7%8E%AF%E5%88%9B%E5%BB%BA.md "Spring--SpringBean循环创建") 9 | 10 | 11 | [Spring--几种代理的实现方式](https://github.com/wuxiaobo000111/Java--apollo/blob/master/%E9%9D%A2%E8%AF%95%E9%A2%98/Spring/%E5%87%A0%E7%A7%8D%E4%BB%A3%E7%90%86%E7%9A%84%E5%AE%9E%E7%8E%B0%E6%96%B9%E5%BC%8F.md "Spring--几种代理的实现方式") 12 | 13 | [Spring--SpringAOP](https://github.com/wuxiaobo000111/Java--apollo/blob/master/%E9%9D%A2%E8%AF%95%E9%A2%98/Spring/SpringAOP.md "Spring--SpringAOP") 14 | 15 | [Spring--Spring事务](https://github.com/wuxiaobo000111/Java--apollo/blob/master/%E9%9D%A2%E8%AF%95%E9%A2%98/Spring/Spring%E4%BA%8B%E5%8A%A1.md "Spring--Spring事务") -------------------------------------------------------------------------------- /jdk/JVM相关/JDK7和JDK8的内存区域划分.md: -------------------------------------------------------------------------------- 1 | ![在这里插入图片描述](https://github.com/wuxiaobo000111/pictures/blob/master/2019-05-07/1.png?raw=true) 2 | 3 | ![在这里插入图片描述](https://github.com/wuxiaobo000111/pictures/blob/master/2019-05-07/2.jpg?raw=true) 4 | 5 | ![在这里插入图片描述](https://github.com/wuxiaobo000111/pictures/blob/master/2019-05-07/3.png?raw=true) -------------------------------------------------------------------------------- /jdk/NIO/NIO/NIO之 通道之间的数据传输.md: -------------------------------------------------------------------------------- 1 | 转载于: 2 | 3 | 4 | 在Java NIO中,如果两个通道中有一个是FileChannel,那你可以直接将数据从一个channel(译者注:channel中文常译作通道)传输到另外一个channel。 5 | 6 | 7 | # transferFrom() 8 | 9 |     FileChannel的transferFrom()方法可以将数据从源通道传输到FileChannel中(译者注:这个方法在JDK文档中的解释为将字节从给定的可读取字节通道传输到此通道的文件中)。下面是一个简单的例子: 10 | 11 | 12 | ```java 13 | RandomAccessFile fromFile = new RandomAccessFile("fromFile.txt", "rw"); 14 | FileChannel fromChannel = fromFile.getChannel(); 15 | RandomAccessFile toFile = new RandomAccessFile("toFile.txt", "rw"); 16 | FileChannel toChannel = toFile.getChannel(); 17 | long position = 0; 18 | long count = fromChannel.size(); 19 | toChannel.transferFrom(position, count, fromChannel); 20 | ``` 21 | 22 |     方法的输入参数position表示从position处开始向目标文件写入数据,count表示最多传输的字节数。如果源通道的剩余空间小于 count 个字节,则所传输的字节数要小于请求的字节数。此外要注意,在SoketChannel的实现中,SocketChannel只会传输此刻准备好的数据(可能不足count字节)。因此,SocketChannel可能不会将请求的所有数据(count个字节)全部传输到FileChannel中。 23 | 24 | 25 | # transferTo() 26 | 27 |     transferTo()方法将数据从FileChannel传输到其他的channel中。下面是一个简单的例子: 28 | 29 | 30 | ```java 31 | 32 | RandomAccessFile fromFile = new RandomAccessFile("fromFile.txt", "rw"); 33 | FileChannel fromChannel = fromFile.getChannel(); 34 | RandomAccessFile toFile = new RandomAccessFile("toFile.txt", "rw"); 35 | FileChannel toChannel = toFile.getChannel(); 36 | long position = 0; 37 | long count = fromChannel.size(); 38 | fromChannel.transferTo(position, count, toChannel); 39 | ``` 40 | 41 |     上面所说的关于SocketChannel的问题在transferTo()方法中同样存在。SocketChannel会一直传输数据直到目标buffer被填满。 -------------------------------------------------------------------------------- /jdk/NIO/NIO/NIO之Channel.md: -------------------------------------------------------------------------------- 1 | 转载于:https://ifeve.com/channels/ 2 | 3 | # 概述 4 | 5 | NIO和IO的区别 6 | 7 | ```text 8 | 1. 既可以从通道中读取数据,又可以写数据到通道。但流的读写通常是单向的。 9 | 10 | 2. 通道可以异步地读写。 11 | 12 | 3. 通道中的数据总是要先读到一个Buffer,或者总是要从一个Buffer中写入。 13 | ``` 14 | 15 | 16 | ![在这里插入图片描述](https://github.com/wuxiaobo000111/pictures/blob/master/2019-05-10/79.png?raw=true) 17 | 18 | 19 | # Channel类型 20 | 21 | 22 | ```text 23 | 1. FileChannel : 从文件中读写数据。 24 | 25 | 2. DatagramChannel : 能通过UDP读写网络中的数据。 26 | 27 | 3. SocketChannel : 能通过TCP读写网络中的数据。 28 | 29 | 4. ServerSocketChannel : 可以监听新进来的TCP连接,像Web服务器那样。对每一个新进来的连接都会创建一个SocketChannel。 30 | ``` 31 | 32 | # 给出一个示例 33 | 34 | 35 | ```java 36 | 37 | 38 | public static void main(String[] args) throws Exception { 39 | RandomAccessFile aFile = new RandomAccessFile("文件名", "rw"); 40 | FileChannel inChannel = aFile.getChannel(); 41 | ByteBuffer buf = ByteBuffer.allocate(1024); 42 | int bytesRead = inChannel.read(buf); 43 | while (bytesRead != -1) { 44 | System.out.println("Read " + bytesRead); 45 | buf.flip(); 46 | while(buf.hasRemaining()){ 47 | System.out.print((char) buf.get()); 48 | } 49 | buf.clear(); 50 | bytesRead = inChannel.read(buf); 51 | } 52 | aFile.close(); 53 | } 54 | ``` 55 | -------------------------------------------------------------------------------- /jdk/NIO/NIO/NIO之FileChannel.md: -------------------------------------------------------------------------------- 1 | 转载于:https://ifeve.com/file-channel/ 2 | 3 | # 概述 4 | 5 |     Java NIO中的FileChannel是一个连接到文件的通道。可以通过文件通道读写文件。FileChannel无法设置为非阻塞模式,它总是运行在阻塞模式下。 6 | 7 | 8 | # 示例 9 | 10 | ```java 11 | 12 | public static void test01 () throws Exception { 13 | RandomAccessFile aFile = new RandomAccessFile("文件路径", "rw"); 14 | FileChannel inChannel = aFile.getChannel(); 15 | ByteBuffer buf = ByteBuffer.allocate(1024); 16 | FileOutputStream fileOutputStream = new FileOutputStream(new File("D:\\wuxiaobo.log")); 17 | FileChannel fileOutputStreamChannel = fileOutputStream.getChannel(); 18 | int bytesRead = inChannel.read(buf); 19 | while (bytesRead != -1) { 20 | buf.flip(); 21 | while(buf.hasRemaining()){ 22 | fileOutputStreamChannel.write(buf); 23 | } 24 | buf.clear(); 25 | bytesRead = inChannel.read(buf); 26 | } 27 | aFile.close(); 28 | } 29 | ``` 30 | 31 | 32 | # 其他方法 33 | 34 | 35 | ## position 36 | 37 | 38 |     有时可能需要在FileChannel的某个特定位置进行数据的读/写操作。可以通过调用position()方法获取FileChannel的当前位置。也可以通过调用position(long pos)方法设置FileChannel的当前位置。这里有两个例子: 39 | ```java 40 | long pos = channel.position(); 41 | channel.position(pos +123); 42 | ``` 43 |     如果将位置设置在文件结束符之后,然后试图从文件通道中读取数据,读方法将返回-1 —— 文件结束标志。如果将位置设置在文件结束符之后,然后向通道中写数据,文件将撑大到当前位置并写入数据。这可能导致“文件空洞”,磁盘上物理文件中写入的数据间有空隙。 44 | 45 | ## size 46 | 47 | FileChannel实例的size()方法将返回该实例所关联文件的大小。如: 48 | 49 | 50 | ```java 51 | long fileSize = channel.size(); 52 | ``` 53 | 54 | ## truncate 55 | 56 | 可以使用FileChannel.truncate()方法截取一个文件。截取文件时,文件将中指定长度后面的部分将被删除。如: 57 | 58 | ```java 59 | channel.truncate(1024); 60 | // 这个例子截取文件的前1024个字节。 61 | ``` 62 | 63 | 64 | ## force方法 65 | 66 | FileChannel.force()方法将通道里尚未写入磁盘的数据强制写到磁盘上。出于性能方面的考虑,操作系统会将数据缓存在内存中,所以无法保证写入到FileChannel里的数据一定会即时写到磁盘上。要保证这一点,需要调用force()方法。force()方法有一个boolean类型的参数,指明是否同时将文件元数据(权限信息等)写到磁盘上。下面的例子同时将文件数据和元数据强制写到磁盘上: 67 | 68 | ```java 69 | channel.force(true); 70 | ``` 71 | -------------------------------------------------------------------------------- /jdk/NIO/NIO/NIO之Pipe.md: -------------------------------------------------------------------------------- 1 | 转载于:https://ifeve.com/pipe/ 2 | 3 | # 概述 4 | 5 | Java NIO 管道是2个线程之间的单向数据连接。Pipe有一个source通道和一个sink通道。数据会被写到sink通道,从source通道读取。 6 | 7 | 8 | ![在这里插入图片描述](https://github.com/wuxiaobo000111/pictures/blob/master/2019-05-10/87.png?raw=true) 9 | 10 | 11 | # 创建管道 12 | 13 | ```java 14 | Pipe pipe = Pipe.open(); 15 | 16 | ``` 17 | 18 | # 向管道写数据 19 | 20 | ```java 21 | Pipe.SinkChannel sinkChannel = pipe.sink(); 22 | String newData = "New String to write to file..." + System.currentTimeMillis(); 23 | ByteBuffer buf = ByteBuffer.allocate(48); 24 | buf.clear(); 25 | buf.put(newData.getBytes()); 26 | buf.flip(); 27 | 28 | while(buf.hasRemaining()) { 29 | sinkChannel.write(buf); 30 | } 31 | 32 | 33 | ``` 34 | 35 | # 从管道读数据 36 | 37 | ```java 38 | Pipe.SourceChannel sourceChannel = pipe.source(); 39 | ByteBuffer buf = ByteBuffer.allocate(48); 40 | int bytesRead = sourceChannel.read(buf); 41 | ``` -------------------------------------------------------------------------------- /jdk/NIO/NIO/NIO之Scatter和Gather.md: -------------------------------------------------------------------------------- 1 | 转载于: https://ifeve.com/java-nio-scattergather/ 2 | 3 |     Java NIO开始支持scatter/gather,scatter/gather用于描述从Channel(译者注:Channel在中文经常翻译为通道)中读取或者写入到Channel的操作。 4 |     分散(scatter)从Channel中读取是指在读操作时将读取的数据写入多个buffer中。因此,Channel将从Channel中读取的数据“分散(scatter)”到多个Buffer中。 5 |     聚集(gather)写入Channel是指在写操作时将多个buffer的数据写入同一个Channel,因此,Channel 将多个Buffer中的数据“聚集(gather)”后发送到Channel。 6 |     scatter / gather经常用于需要将传输的数据分开处理的场合,例如传输一个由消息头和消息体组成的消息,你可能会将消息体和消息头分散到不同的buffer中,这样你可以方便的处理消息头和消息体。 7 | 8 | 9 | # scatter 10 | 11 | 12 | ![在这里插入图片描述](https://github.com/wuxiaobo000111/pictures/blob/master/2019-05-10/85.png?raw=true) 13 | 14 | 代码示例 15 | 16 | ```java 17 | ByteBuffer header = ByteBuffer.allocate(128); 18 | ByteBuffer body = ByteBuffer.allocate(1024); 19 | ByteBuffer[] bufferArray = { header, body }; 20 | channel.read(bufferArray); 21 | ``` 22 |     注意buffer首先被插入到数组,然后再将数组作为channel.read() 的输入参数。read()方法按照buffer在数组中的顺序将从channel中读取的数据写入到buffer,当一个buffer被写满后,channel紧接着向另一个buffer中写。 23 |     Scattering Reads在移动下一个buffer前,必须填满当前的buffer,这也意味着它不适用于动态消息(译者注:消息大小不固定)。换句话说,如果存在消息头和消息体,消息头必须完成填充(例如 128byte),Scattering Reads才能正常工作。 24 | 25 | 26 | # gather 27 | 28 | ![在这里插入图片描述](https://github.com/wuxiaobo000111/pictures/blob/master/2019-05-10/86.png?raw=true) 29 | 30 | 31 | 示例代码: 32 | 33 | ```java 34 | ByteBuffer header = ByteBuffer.allocate(128); 35 | ByteBuffer body = ByteBuffer.allocate(1024); 36 | //write data into buffers 37 | ByteBuffer[] bufferArray = { header, body }; 38 | channel.write(bufferArray); 39 | ``` 40 | 41 |     buffers数组是write()方法的入参,write()方法会按照buffer在数组中的顺序,将数据写入到channel,注意只有position和limit之间的数据才会被写入。因此,如果一个buffer的容量为128byte,但是仅仅包含58byte的数据,那么这58byte的数据将被写入到channel中。因此与Scattering Reads相反,Gathering Writes能较好的处理动态消息。 -------------------------------------------------------------------------------- /jdk/NIO/NIO/NIO之ServerSocketChannel.md: -------------------------------------------------------------------------------- 1 | 2 | # 示例代码 3 | 4 | ```java 5 | import java.io.IOException; 6 | import java.net.InetSocketAddress; 7 | import java.nio.ByteBuffer; 8 | import java.nio.channels.SelectionKey; 9 | import java.nio.channels.Selector; 10 | import java.nio.channels.ServerSocketChannel; 11 | import java.nio.channels.SocketChannel; 12 | import java.util.Iterator; 13 | 14 | public class NIOServer { 15 | 16 | /** 17 | * 选择器 18 | */ 19 | private Selector selector; 20 | 21 | /** 22 | * 通道 23 | */ 24 | ServerSocketChannel serverSocketChannel; 25 | 26 | public void initServer(int port) throws IOException 27 | { 28 | //打开一个通道 29 | serverSocketChannel = ServerSocketChannel.open(); 30 | 31 | //通道设置非阻塞 32 | serverSocketChannel.configureBlocking(false); 33 | 34 | //绑定端口号 35 | serverSocketChannel.socket().bind(new InetSocketAddress("localhost", port)); 36 | 37 | //注册 38 | this.selector = Selector.open(); 39 | serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT); 40 | } 41 | 42 | public void listen() throws IOException 43 | { 44 | System.out.println("server started succeed!"); 45 | 46 | while (true) 47 | { 48 | selector.select(); 49 | Iterator ite = selector.selectedKeys().iterator(); 50 | while (ite.hasNext()) 51 | { 52 | SelectionKey key = ite.next(); 53 | if (key.isAcceptable()) 54 | { 55 | SocketChannel channel = serverSocketChannel.accept(); 56 | channel.configureBlocking(false); 57 | channel.register(selector, SelectionKey.OP_READ); 58 | } 59 | else if (key.isReadable()) 60 | { 61 | recvAndReply(key); 62 | } 63 | ite.remove(); 64 | } 65 | } 66 | } 67 | 68 | public void recvAndReply(SelectionKey key) throws IOException 69 | { 70 | SocketChannel channel = (SocketChannel) key.channel(); 71 | ByteBuffer buffer = ByteBuffer.allocate(256); 72 | int i = channel.read(buffer); 73 | if (i != -1) 74 | { 75 | String msg = new String(buffer.array()).trim(); 76 | System.out.println("NIO server received message = " + msg); 77 | System.out.println("NIO server reply = " + msg); 78 | channel.write(ByteBuffer.wrap( msg.getBytes())); 79 | } 80 | else 81 | { 82 | channel.close(); 83 | } 84 | } 85 | 86 | public static void main(String[] args) throws IOException 87 | { 88 | NIOServer server = new NIOServer(); 89 | server.initServer(10000); 90 | server.listen(); 91 | } 92 | 93 | } 94 | 95 | ``` -------------------------------------------------------------------------------- /jdk/NIO/NIO/NIO之SocketChannel.md: -------------------------------------------------------------------------------- 1 | # 概述 2 |     Java NIO中的SocketChannel是一个连接到TCP网络套接字的通道。可以通过以下2种方式创建SocketChannel。 3 | 4 | 5 | # 代码示例 6 | 7 | ```java 8 | import java.io.IOException; 9 | import java.net.InetSocketAddress; 10 | import java.nio.ByteBuffer; 11 | import java.nio.channels.SocketChannel; 12 | 13 | public class NIOClient { 14 | /** 15 | * 通道 16 | */ 17 | SocketChannel channel; 18 | 19 | public void initClient(String host, int port) throws IOException 20 | { 21 | //构造socket连接 22 | InetSocketAddress servAddr = new InetSocketAddress(host, port); 23 | 24 | //打开连接 25 | this.channel = SocketChannel.open(servAddr); 26 | } 27 | 28 | public void sendAndRecv(String words) throws IOException 29 | { 30 | byte[] msg = new String(words).getBytes(); 31 | ByteBuffer buffer = ByteBuffer.wrap(msg); 32 | System.out.println("Client sending: " + words); 33 | channel.write(buffer); 34 | buffer.clear(); 35 | channel.read(buffer); 36 | System.out.println("Client received: " + new String(buffer.array()).trim()); 37 | 38 | channel.close(); 39 | } 40 | 41 | public static void main(String[] args) throws IOException 42 | { 43 | NIOClient client = new NIOClient(); 44 | client.initClient("localhost", 10000); 45 | client.sendAndRecv("I am a client"); 46 | } 47 | } 48 | 49 | ``` -------------------------------------------------------------------------------- /jdk/NIO/三种IO的总结.md: -------------------------------------------------------------------------------- 1 | >https://blog.csdn.net/caohongshuang/article/details/79455391 2 | 3 | # BIO 4 | ## 传统的BIO编程 5 | 6 |     网络编程的基本模型是C/S模型,即两个进程间的通信。 7 | 8 |     服务端提供IP和监听端口,客户端通过连接操作想服务端监听的地址发起连接请求,通过三次握手连接,如果连接成功建立,双方就可以通过套接字进行通信。 9 | 10 |     传统的同步阻塞模型开发中,ServerSocket负责绑定IP地址,启动监听端口;Socket负责发起连接操作。连接成功后,双方通过输入和输出流进行同步阻塞式通信。 11 | 12 |     简单的描述一下BIO的服务端通信模型:采用BIO通信模型的服务端,通常由一个独立的Acceptor线程负责监听客户端的连接,它接收到客户端连接请求之后为每个客户端创建一个新的线程进行链路处理没处理完成后,通过输出流返回应答给客户端,线程销毁。即典型的一请求一应答模型。 13 | 14 | 15 | 16 | ![在这里插入图片描述](https://github.com/wuxiaobo000111/pictures/blob/master/2019-05-10/78.png?raw=true) 17 | 18 |     该模型最大的问题就是缺乏弹性伸缩能力,当客户端并发访问量增加后,服务端的线程个数和客户端并发访问数呈1:1的正比关系,Java中的线程也是比较宝贵的系统资源,线程数量快速膨胀后,系统的性能将急剧下降,随着访问量的继续增大,系统最终就死-掉-了。 19 | 20 | # NIO 21 | 22 |     JDK 1.4中的java.nio.*包中引入新的Java I/O库,其目的是提高速度。实际上,“旧”的I/O包已经使用NIO重新实现过,即使我们不显式的使用NIO编程,也能从中受益。速度的提高在文件I/O和网络I/O中都可能会发生,但本文只讨论后者。 23 | 24 |     NIO我们一般认为是New I/O(也是官方的叫法),因为它是相对于老的I/O类库新增的(其实在JDK 1.4中就已经被引入了,但这个名词还会继续用很久,即使它们在现在看来已经是“旧”的了,所以也提示我们在命名时,需要好好考虑),做了很大的改变。但民间跟多人称之为Non-block I/O,即非阻塞I/O,因为这样叫,更能体现它的特点。而下文中的NIO,不是指整个新的I/O库,而是非阻塞I/O。 25 | 26 |    NIO提供了与传统BIO模型中的Socket和ServerSocket相对应的SocketChannel和ServerSocketChannel两种不同的套接字通道实现。新增的着两种通道都支持阻塞和非阻塞两种模式。阻塞模式使用就像传统中的支持一样,比较简单,但是性能和可靠性都不好;非阻塞模式正好与之相反。对于低负载、低并发的应用程序,可以使用同步阻塞I/O来提升开发速率和更好的维护性;对于高负载、高并发的(网络)应用,应使用NIO的非阻塞模式来开发。 27 | 28 | ## 基础知识 29 | 30 | ### 缓冲区 Buffer 31 | 32 |      Buffer是一个对象,包含一些要写入或者读出的数据。在NIO库中,所有数据都是用缓冲区处理的。在读取数据时,它是直接读到缓冲区中的;在写入数据时,也是写入到缓冲区中。任何时候访问NIO中的数据,都是通过缓冲区进行操作。缓冲区实际上是一个数组,并提供了对数据结构化访问以及维护读写位置等信息。具体的缓存区有这些:ByteBuffe、CharBuffer、 ShortBuffer、IntBuffer、LongBuffer、FloatBuffer、DoubleBuffer。他们实现了相同的接口:Buffer。 33 | 34 | ![在这里插入图片描述](https://github.com/wuxiaobo000111/pictures/blob/master/2019-05-10/79.png?raw=true) 35 | 36 | ### 通道 Channel 37 | 38 |     我们对数据的读取和写入要通过Channel,它就像水管一样,是一个通道。通道不同于流的地方就是通道是双向的,可以用于读、写和同时读写操作。底层的操作系统的通道一般都是全双工的,所以全双工的Channel比流能更好的映射底层操作系统的API。Channel主要分两大类:SelectableChannel:用户网络读写和FileChannel:用于文件操作后面代码会涉及的ServerSocketChannel和SocketChannel都是SelectableChannel的子类。 39 | 40 | 41 | ![在这里插入图片描述](https://github.com/wuxiaobo000111/pictures/blob/master/2019-05-10/80.png?raw=true) 42 | 43 | 44 | ### 多路复用器 Selector 45 | 46 |     Selector是Java NIO 编程的基础。Selector提供选择已经就绪的任务的能力:Selector会不断轮询注册在其上的Channel,如果某个Channel上面发生读或者写事件,这个Channel就处于就绪状态,会被Selector轮询出来,然后通过SelectionKey可以获取就绪Channel的集合,进行后续的I/O操作。一个Selector可以同时轮询多个Channel,因为JDK使用了epoll()代替传统的select实现,所以没有最大连接句柄1024/2048的限制。所以,只需要一个线程负责Selector的轮询,就可以接入成千上万的客户端。 47 | 48 | # AIO 49 | 50 |     NIO 2.0引入了新的异步通道的概念,并提供了异步文件通道和异步套接字通道的实现。异步的套接字通道时真正的异步非阻塞I/O,对应于UNIX网络编程中的事件驱动I/O(AIO)。他不需要过多的Selector对注册的通道进行轮询即可实现异步读写,从而简化了NIO的编程模型。 -------------------------------------------------------------------------------- /jdk/jdk8/Lambda.md: -------------------------------------------------------------------------------- 1 | # Lambda表达式的三部分 2 | 3 | ```java 4 | 1. 参数列表 5 | 2. 箭头:把参数列表和Lambda主体分割开。 6 | 3. Lambda 7 | 8 | filterGreenApples(apples,apple -> "green".equals(apple.getColor())); 9 | 10 | 其中,apple就是参数,"green".equals(apple.getColor())是Lambda主体。 11 | ``` 12 | 13 | # 函数式接口 14 | 15 | >    只是定义一个抽象方法的接口。 -------------------------------------------------------------------------------- /jdk/jdk8/使用Optional.md: -------------------------------------------------------------------------------- 1 | ```java 2 | package com.bobo.basic.jdk8.chapter7; 3 | 4 | import com.bobo.basic.jdk8.Dish; 5 | 6 | import java.util.Optional; 7 | 8 | public class Optional类 { 9 | 10 | public static void test01 () { 11 | Dish dish = new Dish("pork", false, 800, Dish.Type.MEAT); 12 | Optional optionalDish = Optional.of(dish); 13 | if (optionalDish.isPresent()) { 14 | System.out.println(dish.toString()); 15 | } else { 16 | System.out.println("dis is not exits"); 17 | } 18 | optionalDish = Optional.empty(); 19 | if (optionalDish.isPresent()) { 20 | System.out.println(optionalDish.get().toString()); 21 | } else { 22 | System.out.println(optionalDish); 23 | } 24 | } 25 | public static void main(String[] args) { 26 | test01(); 27 | } 28 | } 29 | 30 | ``` -------------------------------------------------------------------------------- /jdk/jdk8/并行流.md: -------------------------------------------------------------------------------- 1 | ```java 2 | package com.bobo.basic.jdk8.chapter7; 3 | 4 | import java.util.stream.IntStream; 5 | import java.util.stream.Stream; 6 | 7 | public class 并行流 { 8 | 9 | 10 | /** 11 | * 演示如何使用并行流 12 | * 注意共享变量的问题,因为是并行计算,千万不要出现共享变量 13 | */ 14 | public static void test01 () { 15 | Long aLong = Stream.iterate(1L, i -> i + 1).limit(100).parallel().reduce(0L, Long::sum); 16 | System.out.println(aLong); 17 | int reduce = IntStream.iterate(1, i -> i + 1).limit(100).parallel().reduce(0, (int1, int2) -> int1 + int2); 18 | System.out.println(reduce); 19 | } 20 | public static void main(String[] args) { 21 | test01(); 22 | } 23 | } 24 | 25 | ``` -------------------------------------------------------------------------------- /jdk/jdk8/日期类/操作解析格式化日期.md: -------------------------------------------------------------------------------- 1 | ```java 2 | package com.bobo.basic.jdk8.date; 3 | 4 | import java.time.Duration; 5 | import java.time.Instant; 6 | import java.time.LocalDate; 7 | import java.time.LocalDateTime; 8 | import java.time.LocalTime; 9 | import java.time.Period; 10 | import java.time.temporal.ChronoField; 11 | 12 | /** 13 | * 关于java8中的日期类: 14 | * LocalDate, LocalTime,Instant,Duration和Period 最最最简单的使用 15 | */ 16 | public class DateTest { 17 | 18 | /** 19 | * LocalDate日期类 20 | */ 21 | public static void test01() { 22 | LocalDate localDate = LocalDate.of(2019, 4, 30); 23 | System.out.println(localDate.toString()); 24 | localDate = LocalDate.now(); 25 | System.out.println(localDate.toString()); 26 | //使用TemporalField来去读LocalDate的值 27 | int year = localDate.get(ChronoField.YEAR); 28 | System.out.println("year: " + year); 29 | } 30 | 31 | /** 32 | * LocalTime的用法 33 | */ 34 | public static void test02() { 35 | LocalTime localTime = LocalTime.of(15, 31, 20); 36 | System.out.println(localTime.toString()); 37 | int hour = localTime.getHour(); 38 | int minute = localTime.getMinute(); 39 | int second = localTime.getSecond(); 40 | System.out.println("hour:" + hour + ", minute: " + minute + ", second: " + second); 41 | LocalTime parse = LocalTime.parse("15:32:00"); 42 | System.out.println(parse.toString()); 43 | } 44 | 45 | /** 46 | * LocalDateTime的用法 47 | */ 48 | public static void test03() { 49 | LocalDateTime localDateTime = LocalDateTime.now(); 50 | System.out.println(localDateTime.toString()); 51 | LocalDate localDate = LocalDate.of(2019, 4, 30); 52 | LocalTime localTime = LocalTime.of(15, 31, 20); 53 | localDateTime = LocalDateTime.of(localDate, localTime); 54 | System.out.println(localDateTime.toString()); 55 | System.out.println(localDateTime.toLocalDate().toString() + " " + localDateTime.toLocalTime().toString()); 56 | } 57 | 58 | /** 59 | * Instant的用法 60 | */ 61 | public static void test04() { 62 | //从1970年1月1日之后加上 63 | Instant instant = Instant.ofEpochSecond(3); 64 | System.out.println(instant.toString()); 65 | //注意这里会抛出异常信息 66 | int i = Instant.now().get(ChronoField.DAY_OF_MONTH); 67 | System.out.println(i); 68 | } 69 | 70 | /** 71 | * Duration的用法:主要用于以秒或者是纳秒来衡量时间的长短。如果需要比较日期,则可以使用Period类 72 | */ 73 | public static void test05() { 74 | Duration between = Duration.between(LocalTime.of(15, 46, 0), LocalTime.now()); 75 | System.out.println(between.getSeconds()); 76 | Period period = Period.between(LocalDate.of(2018, 4, 30), LocalDate.of(2019, 3, 29)); 77 | System.out.println(period.getYears()); 78 | System.out.println(period.getMonths()); 79 | System.out.println(period.getDays()); 80 | } 81 | 82 | public static void main(String[] args) { 83 | test05(); 84 | } 85 | } 86 | ``` -------------------------------------------------------------------------------- /jdk/jdk8/日期类/日期类.md: -------------------------------------------------------------------------------- 1 | ```java 2 | package com.bobo.basic.jdk8.date; 3 | 4 | import java.time.Duration; 5 | import java.time.Instant; 6 | import java.time.LocalDate; 7 | import java.time.LocalDateTime; 8 | import java.time.LocalTime; 9 | import java.time.Period; 10 | import java.time.temporal.ChronoField; 11 | 12 | /** 13 | * 关于java8中的日期类: 14 | * LocalDate, LocalTime,Instant,Duration和Period 最最最简单的使用 15 | */ 16 | public class DateTest { 17 | 18 | /** 19 | * LocalDate日期类 20 | */ 21 | public static void test01() { 22 | LocalDate localDate = LocalDate.of(2019, 4, 30); 23 | System.out.println(localDate.toString()); 24 | localDate = LocalDate.now(); 25 | System.out.println(localDate.toString()); 26 | //使用TemporalField来去读LocalDate的值 27 | int year = localDate.get(ChronoField.YEAR); 28 | System.out.println("year: " + year); 29 | } 30 | 31 | /** 32 | * LocalTime的用法 33 | */ 34 | public static void test02() { 35 | LocalTime localTime = LocalTime.of(15, 31, 20); 36 | System.out.println(localTime.toString()); 37 | int hour = localTime.getHour(); 38 | int minute = localTime.getMinute(); 39 | int second = localTime.getSecond(); 40 | System.out.println("hour:" + hour + ", minute: " + minute + ", second: " + second); 41 | LocalTime parse = LocalTime.parse("15:32:00"); 42 | System.out.println(parse.toString()); 43 | } 44 | 45 | /** 46 | * LocalDateTime的用法 47 | */ 48 | public static void test03() { 49 | LocalDateTime localDateTime = LocalDateTime.now(); 50 | System.out.println(localDateTime.toString()); 51 | LocalDate localDate = LocalDate.of(2019, 4, 30); 52 | LocalTime localTime = LocalTime.of(15, 31, 20); 53 | localDateTime = LocalDateTime.of(localDate, localTime); 54 | System.out.println(localDateTime.toString()); 55 | System.out.println(localDateTime.toLocalDate().toString() + " " + localDateTime.toLocalTime().toString()); 56 | } 57 | 58 | /** 59 | * Instant的用法 60 | */ 61 | public static void test04() { 62 | //从1970年1月1日之后加上 63 | Instant instant = Instant.ofEpochSecond(3); 64 | System.out.println(instant.toString()); 65 | //注意这里会抛出异常信息 66 | int i = Instant.now().get(ChronoField.DAY_OF_MONTH); 67 | System.out.println(i); 68 | } 69 | 70 | /** 71 | * Duration的用法:主要用于以秒或者是纳秒来衡量时间的长短。如果需要比较日期,则可以使用Period类 72 | */ 73 | public static void test05() { 74 | Duration between = Duration.between(LocalTime.of(15, 46, 0), LocalTime.now()); 75 | System.out.println(between.getSeconds()); 76 | Period period = Period.between(LocalDate.of(2018, 4, 30), LocalDate.of(2019, 3, 29)); 77 | System.out.println(period.getYears()); 78 | System.out.println(period.getMonths()); 79 | System.out.println(period.getDays()); 80 | } 81 | 82 | public static void main(String[] args) { 83 | test05(); 84 | } 85 | } 86 | ``` -------------------------------------------------------------------------------- /jdk/jdk8/流/CompletableFuture.md: -------------------------------------------------------------------------------- 1 | ```java 2 | package com.bobo.basic.jdk8.chapter11; 3 | 4 | public class DelayUtil { 5 | 6 | public static void delay () { 7 | try { 8 | Thread.sleep(1000L); 9 | } catch (InterruptedException e) { 10 | e.printStackTrace(); 11 | } 12 | } 13 | } 14 | 15 | package com.bobo.basic.jdk8.chapter11; 16 | 17 | import java.util.Random; 18 | import java.util.concurrent.CompletableFuture; 19 | import java.util.concurrent.ConcurrentHashMap; 20 | import java.util.concurrent.Future; 21 | import java.util.concurrent.locks.Lock; 22 | import java.util.concurrent.locks.ReentrantLock; 23 | 24 | public class Shop { 25 | 26 | private String name; 27 | 28 | public void setName(String name) { 29 | this.name = name; 30 | } 31 | 32 | public String getName() { 33 | return name; 34 | } 35 | 36 | public Shop() { 37 | 38 | } 39 | public Shop(String name) { 40 | this.name = name; 41 | } 42 | 43 | public static void main(String[] args) throws Exception { 44 | Shop shop = new Shop("BestShop"); 45 | Future myFavoriteProduct = shop.getPriceAsync("my favorite product"); 46 | // 这个get注意:要么获取到返回值,如果没有返回值的话就会在一直阻塞中。 47 | Double aDouble = myFavoriteProduct.get(); 48 | System.out.println("price: "+ aDouble); 49 | } 50 | public double getPrice (String product) { 51 | return calculatePrice(product); 52 | } 53 | 54 | public Future getPriceAsync (String product) { 55 | CompletableFuture future = new CompletableFuture<>(); 56 | new Thread(() ->{ 57 | try { 58 | double calculatePrice = calculatePrice(product); 59 | future.complete(calculatePrice); 60 | }catch (Exception e) { 61 | //异常处理 62 | future.completeExceptionally(e); 63 | } 64 | }).start(); 65 | 66 | ReentrantLock lock = new ReentrantLock(); 67 | lock.lock(); 68 | return future; 69 | } 70 | private double calculatePrice(String product) { 71 | DelayUtil.delay(); 72 | Random random = new Random(); 73 | return random.nextDouble() * product.charAt(0) + product.charAt(1); 74 | } 75 | 76 | 77 | 78 | } 79 | 80 | 81 | ``` -------------------------------------------------------------------------------- /jdk/jdk8/流/分区.md: -------------------------------------------------------------------------------- 1 | ```java 2 | package com.bobo.basic.jdk8.chapter6; 3 | 4 | import com.bobo.basic.jdk8.Dish; 5 | 6 | import java.util.ArrayList; 7 | import java.util.Arrays; 8 | import java.util.List; 9 | import java.util.Map; 10 | import java.util.stream.Collectors; 11 | 12 | public class 分区 { 13 | 14 | 15 | public static List menu = new ArrayList<>(); 16 | 17 | static { 18 | menu = Arrays.asList( 19 | new Dish("pork", false, 800, Dish.Type.MEAT), 20 | new Dish("beef", false, 700, Dish.Type.MEAT), 21 | new Dish("chickren", false, 400, Dish.Type.MEAT), 22 | new Dish("french fries", true, 530, Dish.Type.OTHER), 23 | new Dish("rice", true, 350, Dish.Type.OTHER), 24 | new Dish("season fruit", true, 120, Dish.Type.OTHER), 25 | new Dish("pizza", true, 550, Dish.Type.OTHER), 26 | new Dish("prawns", false, 300, Dish.Type.FISH), 27 | new Dish("salmon", false, 450, Dish.Type.FISH) 28 | ); 29 | } 30 | 31 | 32 | 33 | 34 | public static void test01 () { 35 | Map> collect = menu.stream().collect(Collectors.partitioningBy(Dish::isVegetarian)); 36 | System.out.println(collect.toString()); 37 | } 38 | 39 | public static void test02 () { 40 | Map>> collect = menu.stream().collect(Collectors.partitioningBy(Dish::isVegetarian, Collectors.groupingBy(Dish::getType))); 41 | System.out.println(collect); 42 | } 43 | 44 | /** 45 | * 什么是分区:分区是分组的特殊情况,由一个谓词(返回一个布尔值的函数)作为分类函数,被称为分区函数。分区函数返回一个布尔值, 46 | *分区最多只能分成为两组,一组是true,一组是false. 47 | * 48 | * 分区的好处: 49 | * 保留了分区函数返回true或者false两套流元素列表。同时提供了一个重载的版本。可以产生一个二级map 50 | * @param args 51 | */ 52 | public static void main(String[] args) { 53 | test01(); 54 | } 55 | } 56 | 57 | ``` -------------------------------------------------------------------------------- /jdk/jdk8/流/分组.md: -------------------------------------------------------------------------------- 1 | ```java 2 | package com.bobo.basic.jdk8.chapter6; 3 | 4 | import com.bobo.basic.jdk8.Dish; 5 | 6 | import java.util.ArrayList; 7 | import java.util.Arrays; 8 | import java.util.Comparator; 9 | import java.util.List; 10 | import java.util.Map; 11 | import java.util.Optional; 12 | import java.util.stream.Collectors; 13 | 14 | /** 15 | * @author wuxiaobo 16 | */ 17 | public class 分组 { 18 | public static List menu = new ArrayList<>(); 19 | 20 | public static enum CaloricLevel { 21 | DIET, 22 | NORMAL, 23 | FAT 24 | }; 25 | static { 26 | menu = Arrays.asList( 27 | new Dish("pork", false, 800, Dish.Type.MEAT), 28 | new Dish("beef", false, 700, Dish.Type.MEAT), 29 | new Dish("chickren", false, 400, Dish.Type.MEAT), 30 | new Dish("french fries", true, 530, Dish.Type.OTHER), 31 | new Dish("rice", true, 350, Dish.Type.OTHER), 32 | new Dish("season fruit", true, 120, Dish.Type.OTHER), 33 | new Dish("pizza", true, 550, Dish.Type.OTHER), 34 | new Dish("prawns", false, 300, Dish.Type.FISH), 35 | new Dish("salmon", false, 450, Dish.Type.FISH) 36 | ); 37 | } 38 | 39 | /** 40 | * 这个例子是个一级分类 41 | * Collectors.groupingBy参数中叫做分类函数, 42 | */ 43 | public static void test01 () { 44 | Map> collect = menu.stream().collect(Collectors.groupingBy(Dish::getType)); 45 | System.out.println(collect.toString()); 46 | 47 | System.out.println("====== 一道华丽的分割线 ======="); 48 | Map> listMap = menu.stream().collect(Collectors.groupingBy(dish -> { 49 | if (dish.getCalories() <= 400) { 50 | return CaloricLevel.DIET; 51 | } else if (dish.getCalories() <= 700) { 52 | return CaloricLevel.NORMAL; 53 | } else { 54 | return CaloricLevel.FAT; 55 | } 56 | })); 57 | 58 | System.out.println(listMap.toString()); 59 | } 60 | 61 | 62 | /** 63 | * 多级分类 64 | */ 65 | public static void test02 () { 66 | Map>> collect = menu.stream().collect(Collectors.groupingBy(Dish::getType, Collectors.groupingBy( 67 | dish -> { 68 | if (dish.getCalories() <= 400) { 69 | return CaloricLevel.DIET; 70 | } else if (dish.getCalories() <= 700) { 71 | return CaloricLevel.NORMAL; 72 | } else { 73 | return CaloricLevel.FAT; 74 | } 75 | } 76 | ))); 77 | 78 | System.out.println(collect); 79 | } 80 | 81 | 82 | /** 83 | * 84 | */ 85 | public static void test03 () { 86 | Map collect = menu.stream().collect(Collectors.groupingBy(Dish::getType, Collectors.counting())); 87 | System.out.println(collect); 88 | 89 | System.out.println("======= 一道华丽的分割线 ======="); 90 | Map> collect1 = menu.stream().collect(Collectors.groupingBy(Dish::getType, Collectors.maxBy(Comparator.comparingInt(Dish::getCalories)))); 91 | System.out.println(collect1); 92 | } 93 | 94 | public static void main(String[] args) { 95 | test03(); 96 | } 97 | 98 | } 99 | 100 | ``` -------------------------------------------------------------------------------- /jdk/jdk8/流/收集器.md: -------------------------------------------------------------------------------- 1 | ```java 2 | package com.bobo.basic.jdk8.chapter6; 3 | 4 | 5 | import com.bobo.basic.jdk8.Dish; 6 | 7 | import java.util.ArrayList; 8 | import java.util.Arrays; 9 | import java.util.Comparator; 10 | import java.util.IntSummaryStatistics; 11 | import java.util.List; 12 | import java.util.Optional; 13 | import java.util.stream.Collectors; 14 | 15 | /** 16 | * @author wuxiaobo 17 | */ 18 | public class 收集器 { 19 | 20 | public static List menu = new ArrayList<>(); 21 | 22 | static { 23 | menu = Arrays.asList( 24 | new Dish("pork", false, 800, Dish.Type.MEAT), 25 | new Dish("beef", false, 700, Dish.Type.MEAT), 26 | new Dish("chickren", false, 400, Dish.Type.MEAT), 27 | new Dish("french fries", true, 530, Dish.Type.OTHER), 28 | new Dish("rice", true, 350, Dish.Type.OTHER), 29 | new Dish("season fruit", true, 120, Dish.Type.OTHER), 30 | new Dish("pizza", true, 550, Dish.Type.OTHER), 31 | new Dish("prawns", false, 300, Dish.Type.FISH), 32 | new Dish("salmon", false, 450, Dish.Type.FISH) 33 | ); 34 | } 35 | 36 | public static void test01 () { 37 | List collect = menu.stream().collect(Collectors.toList()); 38 | System.out.println(collect); 39 | } 40 | 41 | 42 | public static void test02 () { 43 | Long aLong = menu.stream().map(Dish::getCalories).collect(Collectors.counting()); 44 | System.out.println(aLong); 45 | } 46 | 47 | 48 | /** 49 | * 查找流中的最大值和最小值 50 | */ 51 | 52 | public static void test03 () { 53 | 54 | // maxby获取最大值 55 | Optional collect = menu.stream().collect(Collectors.maxBy(Comparator.comparingInt(Dish::getCalories))); 56 | if (collect.isPresent()) { 57 | System.out.println(collect.get().toString()); 58 | } 59 | 60 | System.out.println("====== 一道华丽的分割线 ======="); 61 | 62 | // minby获取最小值 63 | Optional collect1 = menu.stream().collect(Collectors.minBy(Comparator.comparingInt(Dish::getCalories))); 64 | if (collect1.isPresent()) 65 | { 66 | System.out.println(collect1.get().toString()); 67 | } 68 | } 69 | 70 | 71 | /** 72 | * 汇总求总数 73 | */ 74 | public static void test04 () { 75 | Integer collect = menu.stream().collect(Collectors.summingInt(Dish::getCalories)); 76 | System.out.println(collect); 77 | } 78 | 79 | 80 | /** 81 | * 汇总,求平均数 82 | */ 83 | public static void test05 () { 84 | Double collect = menu.stream().collect(Collectors.averagingInt(Dish::getCalories)); 85 | System.out.println(collect); 86 | } 87 | 88 | 89 | /** 90 | * 通过summarizingInt可以计算出关于所有的数值操作 91 | * 其他类型用法相似 92 | */ 93 | public static void test06 () { 94 | IntSummaryStatistics collect = menu.stream().collect(Collectors.summarizingInt(Dish::getCalories)); 95 | System.out.println("average:"+collect.getAverage()); 96 | System.out.println("sum:"+collect.getSum()); 97 | } 98 | 99 | 100 | public static void test07 () { 101 | String collect = menu.stream().map(Dish::getName).collect(Collectors.joining(" ")); 102 | System.out.println(collect); 103 | } 104 | 105 | 106 | /** 107 | * 所有的收集器都可以使用Collectors.reducing方法来实现。相当于一个工厂方法,是其他收集器的底层实现 108 | * 1.第一个参数是归约操作的起始值,也就是流中没有元素时候的返回值。 109 | * 2.第二个参数就是要计算的值。 110 | * 3.第三个是将两个项目累计成为一个同类型的值。 111 | * 112 | */ 113 | public static void test08 () { 114 | Integer collect = menu.stream().collect(Collectors.reducing(0, Dish::getCalories, (i, j) -> i + j)); 115 | System.out.println(collect); 116 | 117 | String collect1 = menu.stream().collect(Collectors.reducing("", Dish::getName, (name1, name2) -> name1 + " " + name2)); 118 | 119 | System.out.println(collect1); 120 | } 121 | /** 122 | * 需要将流重组成为集合的时候,就会使用收集器 123 | * @param args 124 | */ 125 | public static void main(String[] args) { 126 | test08(); 127 | } 128 | } 129 | 130 | ``` -------------------------------------------------------------------------------- /jdk/jdk8/流/数值流.md: -------------------------------------------------------------------------------- 1 | ```java 2 | package com.bobo.basic.jdk8.chapter5; 3 | 4 | import com.bobo.basic.jdk8.Dish; 5 | 6 | import java.util.ArrayList; 7 | import java.util.Arrays; 8 | import java.util.List; 9 | import java.util.OptionalInt; 10 | import java.util.stream.Collectors; 11 | import java.util.stream.IntStream; 12 | 13 | public class 数值流 { 14 | 15 | public static List menu = new ArrayList<>(); 16 | 17 | static { 18 | menu = Arrays.asList( 19 | new Dish("pork", false, 800, Dish.Type.MEAT), 20 | new Dish("beef", false, 700, Dish.Type.MEAT), 21 | new Dish("chickren", false, 400, Dish.Type.MEAT), 22 | new Dish("french fries", true, 530, Dish.Type.OTHER), 23 | new Dish("rice", true, 350, Dish.Type.OTHER), 24 | new Dish("season fruit", true, 120, Dish.Type.OTHER), 25 | new Dish("pizza", true, 550, Dish.Type.OTHER), 26 | new Dish("prawns", false, 300, Dish.Type.FISH), 27 | new Dish("salmon", false, 450, Dish.Type.FISH) 28 | ); 29 | } 30 | 31 | 32 | public static void main(String[] args) { 33 | test01(); 34 | } 35 | 36 | /** 37 | * mapToInt方法实现了将流中的元素转化为int类型 38 | * boxed方法又实现了装箱操作 39 | * */ 40 | public static void test01() { 41 | int sum = menu.stream().mapToInt(Dish::getCalories).sum(); 42 | Integer integer = menu.stream().mapToInt(Dish::getCalories).boxed().findAny().get(); 43 | System.out.println(integer); 44 | 45 | OptionalInt max = menu.stream().mapToInt(Dish::getCalories).max(); 46 | } 47 | 48 | public static void test02() { 49 | IntStream intStream = IntStream.rangeClosed(1, 100); 50 | List collect = intStream.boxed().collect(Collectors.toList()); 51 | System.out.println(); 52 | } 53 | } 54 | 55 | ``` -------------------------------------------------------------------------------- /jdk/jdk8/流/流创建的几种方式.md: -------------------------------------------------------------------------------- 1 | ```java 2 | package com.bobo.basic.jdk8.chapter5; 3 | 4 | 5 | import java.io.IOException; 6 | import java.nio.charset.Charset; 7 | import java.nio.file.Files; 8 | import java.nio.file.Paths; 9 | import java.util.Arrays; 10 | import java.util.stream.Stream; 11 | 12 | /** 13 | * @author wuxiaobo 14 | */ 15 | public class 流的创建 { 16 | 17 | 18 | /** 19 | * 通过Stream.of显式的创建一个流 20 | */ 21 | public static void test01 () { 22 | Stream stringStream = Stream.of("Java 8", "Lambda", "In", "Action"); 23 | stringStream.map(String::toUpperCase).forEach(System.out::println); 24 | } 25 | 26 | 27 | /** 28 | * 通过数组创建 29 | */ 30 | public static void test02() { 31 | Integer[] array = {1,2,3,4,5}; 32 | Arrays.stream(array).filter(integer -> integer.intValue()>2).forEach(System.out::println); 33 | } 34 | 35 | 36 | /** 37 | * 通过文件流的方式创建 38 | * @throws IOException 39 | */ 40 | public static void test03 () throws IOException { 41 | Stream lines = Files.lines(Paths.get("data.txt"), Charset.defaultCharset()); 42 | long count = lines.flatMap(line -> 43 | Arrays.stream(line.split(" "))).distinct().count(); 44 | System.out.println(count); 45 | } 46 | 47 | /** 48 | * 通过Stream.iterate创建流 49 | */ 50 | public static void test04 () { 51 | Stream.iterate(0,integer -> integer+2).limit(10).forEach(System.out::println); 52 | System.out.println("=======华丽的分割线"); 53 | } 54 | 55 | 56 | /** 57 | * 通过 Stream.generate来创建流 58 | */ 59 | public static void test05 () { 60 | Stream.generate(Math::random).limit(10).forEach(System.out::println); 61 | } 62 | 63 | 64 | public static void main(String[] args) { 65 | test04(); 66 | } 67 | 68 | } 69 | 70 | ``` -------------------------------------------------------------------------------- /jdk/thread/CAS.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # 模型 4 | >    CAS的全称是Compare And Swap 即比较交换,其算法核心思想如下执行函数:CAS(V,E,N)其包含3个参数: V表示要更新的变量,E表示预期值,N表示新值。 5 | 6 | 7 | >     如果V值等于E值,则将V的值设为N。若V值和E值不同,则说明已经有其他线程做了更新,则当前线程什么都不做。通俗的理解就是CAS操作需要我们提供一个期望值,当期望值与当前线程的变量值相同时,说明还没线程修改该值,当前线程可以进行修改,也就是执行CAS操作,但如果期望值与当前线程不符,则说明该值已被其他线程修改,此时不执行更新操作,但可以选择重新读取该变量再尝试再次修改该变量,也可以放弃操作,原理图如下 8 | 9 | ![在这里插入图片描述](https://github.com/wuxiaobo000111/pictures/blob/master/2019-04-03/3.png?raw=true) 10 | 11 | 12 | >     由于CAS操作属于乐观派,它总认为自己可以成功完成操作,当多个线程同时使用CAS操作一个变量时,只有一个会胜出,并成功更新,其余均会失败,但失败的线程并不会被挂起,仅是被告知失败,并且允许再次尝试,当然也允许失败的线程放弃操作,这点从图中也可以看出来。基于这样的原理,CAS操作即使没有锁,同样知道其他线程对共享资源操作影响,并执行相应的处理措施。同时从这点也可以看出,由于无锁操作中没有锁的存在,因此不可能出现死锁的情况,也就是说无锁操作天生免疫死锁。 13 | 14 | # 如何实现 15 | 16 | >    java中由UnSafe类实现,关于UnSafe的介绍放在了同级目录下。 17 | 18 | -------------------------------------------------------------------------------- /jdk/thread/Exchanger.md: -------------------------------------------------------------------------------- 1 | 2 | --------------------- 3 | 作者:octopusflying 4 | 来源:CSDN 5 | 原文:https://blog.csdn.net/octopusflying/article/details/80634864 6 | 版权声明:本文为博主原创文章,转载请附上博文链接! 7 | 8 | 摘要: 原创出处 http://cmsblogs.com/?p=2269 「小明哥」欢迎转载,保留摘要,谢谢! 9 | # 概述 10 | 11 | >       java.util.concurrent包中的Exchanger类可用于两个线程之间交换信息。可简单地将Exchanger对象理解为一个包含两个格子的容器,通过exchanger方法可以向两个格子中填充信息。当两个格子中的均被填充时,该对象会自动将两个格子的信息交换,然后返回给线程,从而实现两个线程的信息交换。 12 |     另外需要注意的是,Exchanger类仅可用作两个线程的信息交换,当超过两个线程调用同一个exchanger对象时,得到的结果是随机的,exchanger对象仅关心其包含的两个“格子”是否已被填充数据,当两个格子都填充数据完成时,该对象就认为线程之间已经配对成功,然后开始执行数据交换操作。 13 | 14 | 15 | # 示例代码 16 | 17 | ```java 18 | package com.bobo.basic.thread; 19 | 20 | 21 | import java.util.concurrent.Exchanger; 22 | import java.util.concurrent.Semaphore; 23 | 24 | 25 | class ExchangerTest extends Thread { 26 | private Exchanger exchanger; 27 | private String string; 28 | private String threadName; 29 | 30 | public ExchangerTest(Exchanger exchanger, String string, 31 | String threadName) { 32 | this.exchanger = exchanger; 33 | this.string = string; 34 | this.threadName = threadName; 35 | } 36 | 37 | @Override 38 | public void run() { 39 | try { 40 | System.out.println(threadName + ": " + exchanger.exchange(string)); 41 | } catch (InterruptedException e) { 42 | // TODO Auto-generated catch block 43 | e.printStackTrace(); 44 | } 45 | } 46 | } 47 | 48 | public class Test { 49 | public static void main(String[] args) { 50 | Exchanger exchanger = new Exchanger<>(); 51 | ExchangerTest test1 = new ExchangerTest(exchanger, "string1", 52 | "thread-1"); 53 | ExchangerTest test2 = new ExchangerTest(exchanger, "string2", 54 | "thread-2"); 55 | 56 | test1.start(); 57 | test2.start(); 58 | } 59 | } 60 | ``` 61 | >额,原理我就不写了,表示我太菜,有很多地方都看不懂。 -------------------------------------------------------------------------------- /jdk/thread/ThreadLocal源码分析.md: -------------------------------------------------------------------------------- 1 | 2 | ### 源码详解 3 | 4 | >这里针对的是set和get方法 5 | 6 | ### set方法 7 | ```java 8 | 9 | ThreadLocal threadLocal = new ThreadLocal<>(); 10 | threadLocal.set("wuxiaobo"); 11 | threadLocal.set("wuxiaobo"); 12 | ``` 13 | 14 | >首先看一下ThreadLocal的set方法 15 | 16 | ```java 17 | public void set(T value) { 18 | // 首先是拿到当前的线程 19 | Thread t = Thread.currentThread(); 20 | // 然后通过getMap方法 21 | ThreadLocalMap map = getMap(t); 22 | if (map != null) 23 | map.set(this, value); 24 | else 25 | createMap(t, value); 26 | } 27 | 28 | ThreadLocalMap getMap(Thread t) { 29 | return t.threadLocals; 30 | } 31 | 32 | //ThreadLocalMap是ThreadLocal的一个静态内部类,其中看到 33 | // 存储结构是Entry[] table,而Entry的结构如下所示是一个虚引用, 34 | // key是一个ThreadLocal变量,是一个key-value结构的数据结构 35 | // 36 | static class ThreadLocalMap { 37 | static class Entry extends WeakReference> { 38 | /** The value associated with this ThreadLocal. */ 39 | Object value; 40 | 41 | Entry(ThreadLocal k, Object v) { 42 | super(k); 43 | value = v; 44 | } 45 | } 46 | 47 | private static final int INITIAL_CAPACITY = 16; 48 | 49 | /** 50 | * The table, resized as necessary. 51 | * table.length MUST always be a power of two. 52 | */ 53 | private Entry[] table; 54 | 55 | /** 56 | * The number of entries in the table. 57 | */ 58 | private int size = 0; 59 | 60 | /** 61 | * The next size value at which to resize. 62 | */ 63 | private int threshold; // Default to 0 64 | } 65 | ``` 66 | 67 | >然后我们可以具体分析一下map.set(this, value);这个方法,可以看到每次set的时候,都会把ThreadLocal 68 | 这个对象本身传入进去,当做是Entry中的key 69 | ```java 70 | private void set(ThreadLocal key, Object value) { 71 | Entry[] tab = table; 72 | int len = tab.length; 73 | //通过key的哈希得到在数组中的索引值 74 | int i = key.threadLocalHashCode & (len-1); 75 | // 如果相等的话,表示原来有值,现在要做的是替换掉原来的值 76 | // 如果原来没有值,则就会进行赋值操作 77 | for (Entry e = tab[i]; 78 | e != null; 79 | e = tab[i = nextIndex(i, len)]) { 80 | ThreadLocal k = e.get(); 81 | 82 | if (k == key) { 83 | e.value = value; 84 | return; 85 | } 86 | 87 | if (k == null) { 88 | replaceStaleEntry(key, value, i); 89 | return; 90 | } 91 | } 92 | 93 | tab[i] = new Entry(key, value); 94 | int sz = ++size; 95 | //这个我也不知道是干嘛的...... 96 | if (!cleanSomeSlots(i, sz) && sz >= threshold) 97 | rehash(); 98 | } 99 | ``` 100 | >通过之上的分析,我们可以得出结论,其实每次set的时候,就是存放在了当前线程的ThreadLocalMap中, 101 | 这个ThreadLocalMap其实是个数组对象,索引值是通过ThreadLocal本身通过hash得到的。这样的话,每次 102 | 创建一个ThreadLocal对象,只能是存在一个值。 103 | 104 | ### get方法 105 | ```java 106 | public T get() { 107 | Thread t = Thread.currentThread(); 108 | ThreadLocalMap map = getMap(t); 109 | if (map != null) { 110 | ThreadLocalMap.Entry e = map.getEntry(this); 111 | if (e != null) { 112 | @SuppressWarnings("unchecked") 113 | T result = (T)e.value; 114 | return result; 115 | } 116 | } 117 | return setInitialValue(); 118 | } 119 | ``` 120 | >其实还是一样的,每次进来都会拿到当前线程,如果当前线程的ThreadLocalMap不是空,那么久 121 | 通过ThreadLocal对象获取数组下标,然后取值操作。否则调用setInitialValue(),其实源码中放入了一个 122 | null值进去。 123 | ```java 124 | private T setInitialValue() { 125 | T value = initialValue(); 126 | Thread t = Thread.currentThread(); 127 | ThreadLocalMap map = getMap(t); 128 | if (map != null) 129 | map.set(this, value); 130 | else 131 | createMap(t, value); 132 | return value; 133 | } 134 | 135 | protected T initialValue() { 136 | return null; 137 | } 138 | ``` 139 | 140 | 141 | ### 总结 142 | >这就解释了为何ThreadLocal能创建线程独有的局部变量,可以在整个线程存活的过程中随时取用。 143 | -------------------------------------------------------------------------------- /jdk/thread/happens-bofore.md: -------------------------------------------------------------------------------- 1 | 转载: 如未加特殊说明,此网站文章均为原创,转载必须注明出处。Java 技术驿站 » 【死磕Java并发】—–Java内存模型之happens-before 原地址:http://cmsblogs.com/?p=2102 2 | 3 | # 什么是happens-before 4 | 5 | >    在 JMM 中,如果一个操作执行的结果需要对另一个操作可见,那么这两个操作之间必须存在 happens-before 关系。定义如下: 6 | 7 | ```text 8 | 1.如果一个操作 happens-before 另一个操作,那么第一个操作的执行结果,将对第二个操作可见,而且第一个操作的 9 | 执行顺序,排在第二个操作之前。 10 | 2.两个操作之间存在 happens-before 关系,并不意味着一定要按照 happens-before 原则制定的顺序来执行。如果 11 | 重排序之后的执行结果与按照 happens-before 关系来执行的结果一致,那么这种重排序并不非法。 12 | ``` 13 | 14 | >    如下的例子。j 是否等于 1 呢?假定线程 A 的操作(i = 1)happens-before 线程 B 的操作(j = i),那么可以确定,线程 B 执行后 j = 1 一定成立。如果他们不存在 happens-before 原则,那么 j = 1 不一定成立。这就是happens-before原则的威力。 15 | 16 | ```java 17 | 18 | i = 1; // 线程 A 执行 19 | j = i; //线程 B 执行 20 | 21 | ``` 22 | 23 | # 规则 24 | 25 | ```text 26 | 1.程序次序规则:一个线程内,按照代码顺序,书写在前面的操作,happens-before 于书写在后面的操作。 27 | 28 | 2.锁定规则:一个 unLock 操作,happens-before 于后面对同一个锁的 lock 操作。 29 | 30 | 3.volatile 变量规则:对一个变量的写操作,happens-before 于后面对这个变量的读操作。 31 | 32 | 4.传递规则:如果操作 A happens-before 操作 B,而操作 B happens-before 操作C,则可以得出,操作 A happens-before 操作C 33 | 34 | 5.线程启动规则:Thread 对象的 start 方法,happens-before 此线程的每个一个动作。 35 | 36 | 5.线程中断规则:对线程 interrupt 方法的调用,happens-before 被中断线程的代码检测到中断事件的发生。 37 | 38 | 6.线程终结规则:线程中所有的操作,都 happens-before 线程的终止检测,我们可以通过Thread.join() 方法结束、 39 | Thread.isAlive() 的返回值手段,检测到线程已经终止执行。 40 | 41 | 7.对象终结规则:一个对象的初始化完成,happens-before 它的 finalize() 方法的开始 42 | 43 | 44 | 上面八条是原生 Java 满足 happens-before 关系的规则,但是我们可以对他们进行推导出其他满足 happens-before 的规则: 45 | 46 | 1.将一个元素放入一个线程安全的队列的操作,happens-before 从队列中取出这个元素的操作。 47 | 48 | 2.将一个元素放入一个线程安全容器的操作,happens-before 从容器中取出这个元素的操作。 49 | 50 | 3.在 CountDownLatch 上的 countDown 操作,happens-before CountDownLatch 上的 await 操作。 51 | 52 | 4.释放 Semaphore 上的 release 的操作,happens-before 上的 acquire 操作。 53 | 54 | 5.Future 表示的任务的所有操作,happens-before Future 上的 get 操作。 55 | 56 | 6.向 Executor 提交一个 Runnable 或 Callable 的操作,happens-before 任务开始执 57 | ``` -------------------------------------------------------------------------------- /jdk/thread/java内存模型重排序.md: -------------------------------------------------------------------------------- 1 | 原文出处:http://cmsblogs.com/ 『chenssy』 2 | 3 | # 概述 4 | 5 | >    在执行程序时,为了提供性能,处理器和编译器常常会对指令进行重排序,但是不能随意重排序,不是你想怎么排序就怎么排序,它需要满足以下两个条件:在单线程环境下不能改变程序运行的结果;存在数据依赖关系的不允许重排序。 6 | 7 | 8 | # as-if-serial语义 9 | 10 | >    as-if-serial语义的意思是,所有的操作均可以为了优化而被重排序,但是你必须要保证重排序后执行的结果不能被改变,编译器、runtime、处理器都必须遵守as-if-serial语义。注意as-if-serial只保证单线程环境,多线程环境下无效。下面我们用一个简单的示例来说明: 11 | 12 | ```java 13 | 下面我们用一个简单的示例来说明: 14 | 15 | int a = 1 ; //A 16 | int b = 2 ; //B 17 | int c = a + b; //C 18 | ``` 19 | 20 | >    A、B、C三个操作存在如下关系:A、B不存在数据依赖关系,A和C、B和C存在数据依赖关系,因此在进行重排序的时候,A、B可以随意排序,但是必须位于C的前面,执行顺序可以是A –> B –> C或者B –> A –> C。但是无论是何种执行顺序最终的结果C总是等于3。as-if-serail语义把单线程程序保护起来了,它可以保证在重排序的前提下程序的最终结果始终都是一致的。 21 |     1、2是程序顺序次序规则,3是传递性。但是,不是说通过重排序,B可能会排在A之前执行么,为何还会存在存在A happens-beforeB呢?这里再次申明A happens-before B不是A一定会在B之前执行,而是A的对B可见,但是相对于这个程序A的执行结果不需要对B可见,且他们重排序后不会影响结果,所以JMM不会认为这种重排序非法。 22 |     下面我们在看一段有意思的代码: 23 | 24 | ```java 25 | public class RecordExample1 { 26 | public static void main(String[] args){ 27 | int a = 1; 28 | int b = 2; 29 | 30 | try { 31 | a = 3; //A 32 | b = 1 / 0; //B 33 | } catch (Exception e) { 34 | 35 | } finally { 36 | System.out.println("a = " + a); 37 | } 38 | } 39 | } 40 | ``` 41 | >    按照重排序的规则,操作A与操作B有可能会进行重排序,如果重排序了,B会抛出异常( / by zero),此时A语句一定会执行不到,那么a还会等于3么?如果按照as-if-serial原则它就改变了程序的结果。其实JVM对异常做了一种特殊的处理,为了保证as-if-serial语义,Java异常处理机制对重排序做了一种特殊的处理:JIT在重排序时会在catch语句中插入错误代偿代码(a = 3),这样做虽然会导致cathc里面的逻辑变得复杂,但是JIT优化原则是:尽可能地优化程序正常运行下的逻辑,哪怕以catch块逻辑变得复杂为代价。 42 | 43 | >    重排序不会影响单线程环境的执行结果,但是会破坏多线程的执行语义。 -------------------------------------------------------------------------------- /jdk/thread/java锁的升级.md: -------------------------------------------------------------------------------- 1 | ![在这里插入图片描述](https://github.com/wuxiaobo000111/pictures/blob/master/2019-04-01/1.png?raw=true) 2 | 3 | 4 | 5 | >我们都知道在JDK5中出现了Concurrent包,里面的lock锁性能比它好,使用起来也很灵活。这就把synchronized比下去了,在当时synchronized不管什么情况就是加锁、解锁,每次都要切换到内核态来做这些操作,导致CPU浪费了大量时间在线程的调度上。synchronized觉得不这不公平,我性能又差,而且还不灵活,那以后谁还用我!java一看,觉得不能偏心,就在jdk6的时候给synchronized进行了一个大优化,也就是上面提到的:引入偏向锁和轻量级锁。 6 | 7 | ### 偏向锁 8 | 9 | >偏向锁实际上是一种锁优化的,其目的是为了减少数据在无竞争情况下的性能消耗。其核心思想就是锁会偏向第一个获取它的线程,在接下来的执行过程中该锁没有其他的线程获取,则持有偏向锁的线程永远不需要再进行同步。 10 | 11 | 12 | ### 偏向锁的获取 13 | 14 | >当一个线程访问同步块并获取锁时,会在对象头和栈帧中的锁记录里储存锁偏向的线程ID。 15 | 以后该线程在进入和退出同步块时不需要进行CAS操作来加锁和解锁,只需要检查当前Mark 16 | Word中储存的线程是否指向当前线程,如果成功,表示已经获得对象锁;如果检测失败, 17 | 则需要再测试一下Mark Word中偏向锁的标志是否已经被置为1(表示当前锁是偏向锁): 18 | 如果没有则使用CAS操作竞争锁,如果设置了,则尝试使用CAS将对象头的偏向锁指向当前线程。 19 | 20 | 21 | ### 关闭偏向锁 22 | 23 | ```text 24 | 偏向锁在Java运行环境中默认开启,但是不会随着程序启动立即生效,而是在启动几秒种后才激活,可以使用参数关闭延迟: 25 | -XX:BiasedLockingStartupDelay=0  26 | 同样可以关闭偏向锁 27 |  -XX:UseBiasedLocking=false,那么程序默认进入轻量级锁。 28 | ``` 29 | 30 | ### 轻量级锁 31 | 32 | >轻量级锁是JDK1.6之中加入的新型锁机制,它并不是来代替重量级锁的,他的本意是在没有多线程竞争的前提下,减少传统的重量级锁使用操作系统互斥量产生的性能消耗。 33 | 34 | ### 轻量级锁加锁 35 | 36 | >线程在执行同步块之前,JVM会现在当前线程的栈帧中创建用于储存锁记录的空间(LockRecord),并将对象头的Mark Word信息复制到锁记录中。然后线程尝试使用CAS将对象头的MarkWord替换为指向锁记录的指针。如果成功,当前线程获得锁,并且对象的锁标志位转变为“00”,如果失败,表示其他线程竞争锁,当前线程便会尝试自旋获取锁。如果有两条以上的线程竞争同一个锁,那么轻量级锁就不再有效,要膨胀为重量级锁,锁标志的状态变为“10”,MarkWord中储存的就是指向重量级锁(互斥量)的指针,后面等待的线程也要进入阻塞状态。 37 | 38 | 39 | ### 轻量级锁解锁 40 | >轻量级锁解锁时,同样通过CAS操作将对象头换回来。如果成功,则表示没有竞争发生。如果失败,说明有其他线程尝试过获取该锁,锁同样会膨胀为重量级锁。在释放锁的同时,唤醒被挂起的线程。 41 | 42 | 43 | -------------------------------------------------------------------------------- /jdk/thread/volatile 的实现原理.md: -------------------------------------------------------------------------------- 1 | 摘要: 原创出处 http://cmsblogs.com/?p=2092 「小明哥」欢迎转载,保留摘要,谢谢! 2 | # 概述 3 | 4 | >     volatile ,则是轻量级的 synchronized ,它在多线程开发中保证了共享变量的“可见性”。如果一个变量使用 volatile ,则它比使用 synchronized 的成本更加低,因为它不会引起线程上下文的切换和调度。 5 | 6 | # Java内存模型 7 | 8 | >    计算机在运行程序时,每条指令都是在 CPU 中执行的,在执行过程中势必会涉及到数据的读写。我们知道程序运行的数据是存储在主存中,这时就会有一个问题,读写主存中的数据没有 CPU 中执行指令的速度快,如果任何的交互都需要与主存打交道则会大大影响效率,所以就有了 CPU 高速缓存。CPU高速缓存为某个CPU独有,只与在该CPU运行的线程有关。 9 |     有了 CPU 高速缓存虽然解决了效率问题,但是它会带来一个新的问题:数据一致性。在程序运行中,会将运行所需要的数据复制一份到 CPU 高速缓存中,在进行运算时 CPU 不再也主存打交道,而是直接从高速缓存中读写数据,只有当运行结束后,才会将数据刷新到主存中。举一个简单的例子:i=i+1; 10 |     当线程运行这段代码时,首先会从主存中读取 i 的值( 假设此时 i = 1 ),然后复制一份到 CPU 高速缓存中,然后 CPU 执行 + 1 的操作(此时 i = 2),然后将数据 i = 2 写入到告诉缓存中,最后刷新到主存中。实这样做在单线程中是没有问题的,有问题的是在多线程中。如下:假如有两个线程 A、B 都执行这个操作( i++ ),按照我们正常的逻辑思维主存中的i值应该=3 。但事实是这样么?分析如下:两个线程从主存中读取 i 的值( 假设此时 i = 1 ),到各自的高速缓存中,然后线程 A 执行 +1 操作并将结果写入高速缓存中,最后写入主存中,此时主存 i = 2 。线程B做同样的操作,主存中的 i 仍然 =2 。所以最终结果为 2 并不是 3 。这种现象就是缓存一致性问题。 11 | 12 | ## 如何解决缓存一致性问题 13 | 14 | ```text 15 | 通过在总线加 LOCK# 锁的方式 16 | 第一种方案, 存在一个问题,它是采用一种独占的方式来实现的,即总线加 LOCK# 锁的话,只能有一个 CPU 17 | 能够运行,其他 CPU 都得阻塞,效率较为低下。 18 | 通过缓存一致性协议 19 | 第二种方案,缓存一致性协议(MESI 协议),它确保每个缓存中使用的共享变量的副本是一致的。其核心思想如 20 | 下:当某个 CPU 在写数据时,如果发现操作的变量是共享变量,则会通知其他 CPU 告知该变量的缓存行是无 21 | 效的,因此其他 CPU 在读取该变量时,发现其无效会重新从主存中加载数据。 22 | ``` 23 | 24 | # volatile 原理 25 | 26 | >    volatile 可以保证线程可见性且提供了一定的有序性,但是无法保证原子性。在 JVM 底层,volatile 是采用“内存屏障”来实现的。上面那段话,有两层语义:1.保证可见性、不保证原子性。2.禁止指令重排序。 27 | 28 | ## 什么是指令重排序 29 | 30 | >    在执行程序时为了提高性能,编译器和处理器通常会对指令做重排序:1。编译器重排序。编译器在不改变单线程程序语义的前提下,可以重新安排语句的执行顺序。2。处理器重排序。如果不存在数据依赖性,处理器可以改变语句对应机器指令的执行顺序。 31 |     指令重排序对单线程没有什么影响,他不会影响程序的运行结果,但是会影响多线程的正确性。既然指令重排序会影响到多线程执行的正确性,那么我们就需要禁止重排序。 32 | 33 | ## JVM是如何禁止重排序的呢 34 | 35 | ```text 36 | 1.程序次序规则:一个线程内,按照代码顺序,书写在前面的操作,happens-before 于书写在后面的操作。 37 | 38 | 2.锁定规则:一个 unLock 操作,happens-before 于后面对同一个锁的 lock 操作。 39 | 40 | 3.volatile 变量规则:对一个变量的写操作,happens-before 于后面对这个变量的读操作。 41 | 42 | 4.传递规则:如果操作 A happens-before 操作 B,而操作 B happens-before 操作C,则可以得出,操作 A 43 | happens-before 操作C 44 | 45 | 5.线程启动规则:Thread 对象的 start 方法,happens-before 此线程的每个一个动作。 46 | 47 | 6.线程中断规则:对线程 interrupt 方法的调用,happens-before 被中断线程的代码检测到中断事件的发生。 48 | 49 | 7.线程终结规则:线程中所有的操作,都 happens-before 线程的终止检测,我们可以通过Thread.join() 方法结束、 50 | Thread.isAlive() 的返回值手段,检测到线程已经终止执行。 51 | 52 | 8.对象终结规则:一个对象的初始化完成,happens-before 它的 finalize() 方法的开始 53 | ``` 54 | 55 | >    我们着重看第三点 Volatile规则:对 volatile变量的写操作,happen-before 后续的读操作。为了实现 volatile 内存语义,JMM会重排序,其规则如下: 56 | 57 | 58 | ![在这里插入图片描述](https://github.com/wuxiaobo000111/pictures/blob/master/2019-05-10/20.png?raw=true) 59 | 60 | 61 | >    当第二个操作是 volatile 写操作时,不管第一个操作是什么,都不能重排序。这个规则,确保 volatile 写操作之前的操作,都不会被编译器重排序到 volatile 写操作之后。 62 |     观察加入 volatile 关键字和没有加入 volatile 关键字时所生成的汇编代码发现,加入volatile 关键字时,会多出一个 lock 前缀指令。lock 前缀指令,其实就相当于一个内存屏障。内存屏障是一组处理指令,用来实现对内存操作的顺序限制。volatile 的底层就是通过内存屏障来实现的。下图是完成上述规则所需要的内存屏障: 63 | 64 | ![在这里插入图片描述](https://github.com/wuxiaobo000111/pictures/blob/master/2019-05-10/21.png?raw=true) -------------------------------------------------------------------------------- /jdk/thread/多线程的创建方式.md: -------------------------------------------------------------------------------- 1 | ```java 2 | class TestThreadOne extends Thread { 3 | @Override 4 | public void run() { 5 | System.out.println("one"+Thread.currentThread().getName()); 6 | } 7 | } 8 | 9 | class TestThreadSecond implements Runnable { 10 | @Override 11 | public void run() { 12 | System.out.println("second"+ Thread.currentThread().getName()); 13 | } 14 | } 15 | 16 | class TestThreadThird implements Callable { 17 | @Override 18 | public Object call() throws Exception { 19 | return Thread.currentThread().getName(); 20 | } 21 | } 22 | 23 | 24 | 25 | 26 | 27 | TestThreadOne threadOne = new TestThreadOne(); 28 | threadOne.start(); 29 | Thread thread = new Thread(new TestThreadSecond()); 30 | thread.start(); 31 | ExecutorService executorService = new ThreadPoolExecutor(10,10,10L,TimeUnit.SECONDS, 32 | new ArrayBlockingQueue(100)); 33 | Future submit = executorService.submit(new TestThreadThird()); 34 | try { 35 | System.out.println("third"+submit.get()); 36 | } catch (Exception e) { 37 | e.printStackTrace(); 38 | } 39 | try { 40 | Thread.sleep(10000); 41 | } catch (InterruptedException e) { 42 | e.printStackTrace(); 43 | } 44 | executorService.shutdown(); 45 | ``` -------------------------------------------------------------------------------- /jdk/thread/深入分析 CAS.md: -------------------------------------------------------------------------------- 1 | >摘要: 原创出处 http://cmsblogs.com/?p=2235 「小明哥」欢迎转载,保留摘要,谢谢! 2 | 3 | # 概述 4 | 5 | >    CAS,Compare And Swap,即比较并交换。Doug lea大神在同步组件中大量使用CAS技术鬼斧神工地实现了Java多线程的并发操作。整个AQS同步组件、Atomic原子类操作等等都是以CAS实现的,甚至ConcurrentHashMap在1.8的版本中也调整为了CAS+Synchronized。可以说CAS是整个JUC的基石。 6 | 7 | ![在这里插入图片描述](https://github.com/wuxiaobo000111/pictures/blob/master/2019-05-10/36.png?raw=true) 8 | 9 | 10 | # CAS分析 11 | 12 | >    在CAS中有三个参数:内存值V、旧的预期值A、要更新的值B,当且仅当内存值V的值等于旧的预期值A时才会将内存值V的值修改为B,否则什么都不干。其伪代码如下: 13 | 14 | ```java 15 | if(this.value == A){ 16 | this.value = B 17 | return true; 18 | }else{ 19 | return false; 20 | } 21 | ``` 22 | 23 | >    JUC下的atomic类都是通过CAS来实现的,下面就以AtomicInteger为例来阐述CAS的实现。如下: 24 | 25 | ```java 26 | private static final Unsafe unsafe = Unsafe.getUnsafe(); 27 | private static final long valueOffset; 28 | 29 | static { 30 | try { 31 | valueOffset = unsafe.objectFieldOffset 32 | (AtomicInteger.class.getDeclaredField("value")); 33 | } catch (Exception ex) { throw new Error(ex); } 34 | } 35 | 36 | private volatile int value; 37 | ``` 38 | 39 | 40 | >    Unsafe是CAS的核心类,Java无法直接访问底层操作系统,而是通过本地(native)方法来访问。不过尽管如此,JVM还是开了一个后门:Unsafe,它提供了硬件级别的原子操作。 41 |     valueOffset为变量值在内存中的偏移地址,unsafe就是通过偏移地址来得到数据的原值的。 42 |     value当前值,使用volatile修饰,保证多线程环境下看见的是同一个。 43 |     我们就以AtomicInteger的addAndGet()方法来做说明,先看源代码: 44 | 45 | ```java 46 | /** 47 | * Atomically adds the given value to the current value. 48 | * 49 | * @param delta the value to add 50 | * @return the updated value 51 | */ 52 | public final int addAndGet(int delta) { 53 | return unsafe.getAndAddInt(this, valueOffset, delta) + delta; 54 | } 55 | 56 | public final int getAndAddInt(Object var1, long var2, int var4) { 57 | int var5; 58 | do { 59 | var5 = this.getIntVolatile(var1, var2); 60 | } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4)); 61 | 62 | return var5; 63 | } 64 | ``` 65 | 66 | >    该方法为本地方法,有四个参数,分别代表:对象、对象的地址、预期值、修改值(有位伙伴告诉我他面试的时候就问到这四个变量是啥意思…)。该方法的实现这里就不做详细介绍了,有兴趣的伙伴可以看看openjdk的源码。 67 |     CAS可以保证一次的读-改-写操作是原子操作,在单处理器上该操作容易实现,但是在多处理器上实现就有点儿复杂了。 68 |     CPU提供了两种方法来实现多处理器的原子操作:总线加锁或者缓存加锁。 69 | 70 |     CPU提供了两种方法来实现多处理器的原子操作:总线加锁或者缓存加锁。 71 | 72 | ```text 73 | 74 | 总线加锁:总线加锁就是就是使用处理器提供的一个LOCK#信号,当一个处理器在总线上输出此信号时,其他处理器的请求将被阻塞住 75 | ,那么该处理器可以独占使用共享内存。但是这种处理方式显得有点儿霸道,不厚道,他把CPU和内存之间的通信锁住了,在锁定期间 76 | ,其他处理器都不能其他内存地址的数据,其开销有点儿大。所以就有了缓存加锁。 77 | 78 | 缓存加锁:其实针对于上面那种情况我们只需要保证在同一时刻对某个内存地址的操作是原子性的即可。缓存加锁就是缓存在内存区 79 | 域的数据如果在加锁期间,当它执行锁操作写回内存时,处理器不在输出LOCK#信号,而是修改内部的内存地址,利用缓存一致性协 80 | 议来保证原子性。缓存一致性机制可以保证同一个内存区域的数据仅能被一个处理器修改,也就是说当CPU1修改缓存行中的i时使用 81 | 缓存锁定,那么CPU2就不能同时缓存了i的缓存行。 82 | ``` 83 | 84 | 85 | # CAS的缺陷 86 | 87 | >    带来的三个问题 88 | 89 | ```text 90 | 循环时间太长 91 | 92 | 如果CAS一直不成功呢?这种情况绝对有可能发生,如果自旋CAS长时间地不成功,则会给CPU带来非常大的开销。 93 | 在JUC中有些地方就限制了CAS自旋的次数,例如BlockingQueue的SynchronousQueue。 94 | 95 | 只能保证一个共享变量原子操作 96 | 97 | 看了CAS的实现就知道这只能针对一个共享变量,如果是多个共享变量就只能使用锁了,当然如果你有办法把多个变量 98 | 整成一个变量,利用CAS也不错。例如读写锁中state的高地位 99 | 100 | ABA问题 101 | 102 | CAS需要检查操作值有没有发生改变,如果没有发生改变则更新。但是存在这样一种情况:如果一个值原来是A, 103 | 变成了B,然后又变成了A,那么在CAS检查的时候会发现没有改变,但是实质上它已经发生了改变,这就是所谓的 104 | ABA问题。对于ABA问题其解决方案是加上版本号,即在每个变量都加上一个版本号,每次改变时加1,即A —> B —> A, 105 | 变成1A —> 2B —> 3A。 106 | 107 | CAS的ABA隐患问题,解决方案则是版本号,Java提供了AtomicStampedReference来解决。AtomicStampedReference通 108 | 过包装[E,Integer]的元组来对对象标记版本戳stamp,从而避免ABA问题。 109 | ``` -------------------------------------------------------------------------------- /jdk/thread/深入理解ConcurrentSkipListMap.md: -------------------------------------------------------------------------------- 1 | >摘要: 原创出处 http://cmsblogs.com/?p=2371 「小明哥」欢迎转载,保留摘要,谢谢! 2 | 3 | # SkipList 4 | 5 | >    什么是SkipList?Skip List ,称之为跳表,它是一种可以替代平衡树的数据结构,其数据元素默认按照key值升序,天然有序。Skip list让已排序的数据分布在多层链表中,以0-1随机数决定一个数据的向上攀升与否,通过“空间来换取时间”的一个算法,在每个节点中增加了向前的指针,在插入、删除、查找时可以忽略一些不可能涉及到的结点,从而提高了效率。 6 | 7 | >    我们先看一个简单的链表,如下: 8 | ![在这里插入图片描述](https://github.com/wuxiaobo000111/pictures/blob/master/2019-05-10/69.png?raw=true) 9 | 10 | 11 | >   如果我们需要查询9、21、30,则需要比较次数为3 + 6 + 8 = 17 次,那么有没有优化方案呢?有!我们将该链表中的某些元素提炼出来作为一个比较“索引”,如下: 12 | 13 | ![在这里插入图片描述](https://github.com/wuxiaobo000111/pictures/blob/master/2019-05-10/70.png?raw=true) 14 | 15 | >    我们先与这些索引进行比较来决定下一个元素是往右还是下走,由于存在“索引”的缘故,导致在检索的时候会大大减少比较的次数。当然元素不是很多,很难体现出优势,当元素足够多的时候,这种索引结构就会大显身手。 16 | 17 | ## skiplist的特性 18 | 19 | ```text 20 | 1. 由很多层结构组成,level是通过一定的概率随机产生的 21 | 22 | 2. 每一层都是一个有序的链表,默认是升序,也可以根据创建映射时所提供的Comparator进行排序,具体取决于使用的 23 | 构造方法 24 | 25 | 3. 最底层(Level 1)的链表包含所有元素 26 | 27 | 4. 如果一个元素出现在Level i 的链表中,则它在Level i 之下的链表也都会出现 28 | 29 | 5. 每个节点包含两个指针,一个指向同一链表中的下一个元素,一个指向下面一层的元素 30 | ``` 31 | 32 | ![在这里插入图片描述](https://github.com/wuxiaobo000111/pictures/blob/master/2019-05-10/71.png?raw=true) 33 | 34 | ## skiplist的查找 35 | 36 | >    SkipListd的查找算法较为简单,对于上面我们我们要查找元素21,其过程如下: 37 | 38 | ```text 39 | 比较3,大于,往后找(9), 40 | 41 | 比9大,继续往后找(25),但是比25小,则从9的下一层开始找(16) 42 | 43 | 16的后面节点依然为25,则继续从16的下一层找 44 | 45 | 找到21 46 | ``` 47 | 48 | ![在这里插入图片描述](https://github.com/wuxiaobo000111/pictures/blob/master/2019-05-10/72.png?raw=true) 49 | 50 | ## skiplist的插入 51 | 52 | >    SkipList的插入操作主要包括: 53 | 54 | ```text 55 | 查找合适的位置。这里需要明确一点就是在确认新节点要占据的层次K时,采用丢硬币的方式,完全随机。如果 56 | 占据的层次K大于链表的层次,则重新申请新的层,否则插入指定层次 57 | 58 | 申请新的节点 59 | 60 | 调整指针 61 | 62 | ``` 63 | >    假定我们要插入的元素为23,经过查找可以确认她是位于21后,9、16、21前。当然需要考虑申请的层次K。 64 |     如果层次K > 3,需要申请新层次(Level 4) 65 | 66 | ![在这里插入图片描述](https://github.com/wuxiaobo000111/pictures/blob/master/2019-05-10/74.png?raw=true) 67 | 68 | >    如果层次 K = 2,直接在Level 2 层插入即可 69 | 70 | ![在这里插入图片描述](https://github.com/wuxiaobo000111/pictures/blob/master/2019-05-10/75.png?raw=true) 71 | 72 | 73 | ## SkipList的删除 74 | >    删除节点和插入节点思路基本一致:找到节点,删除节点,调整指针。比如删除节点9,如下: 75 | 76 | 77 | ![在这里插入图片描述](https://github.com/wuxiaobo000111/pictures/blob/master/2019-05-10/76.png?raw=true) 78 | 79 | 80 | # ConcurrentSkipListMap 81 | 82 | >    通过上面我们知道SkipList采用空间换时间的算法,其插入和查找的效率O(logn),其效率不低于红黑树,但是其原理和实现的复杂度要比红黑树简单多了。一般来说会操作链表List,就会对SkipList毫无压力。 83 |     ConcurrentSkipListMap其内部采用SkipLis数据结构实现。为了实现SkipList,ConcurrentSkipListMap提供了三个内部类来构建这样的链表结构:Node、Index、HeadIndex。其中Node表示最底层的单链表有序节点、Index表示为基于Node的索引层,HeadIndex用来维护索引层次。到这里我们可以这样说ConcurrentSkipListMap是通过HeadIndex维护索引层次,通过Index从最上层开始往下层查找,一步一步缩小查询范围,最后到达最底层Node时,就只需要比较很小一部分数据了。在JDK中的关系如下图: 84 | 85 | 86 | ![在这里插入图片描述](https://github.com/wuxiaobo000111/pictures/blob/master/2019-05-10/77.png?raw=true) 87 | 88 | 89 | ```java 90 | static final class Node { 91 | final K key; 92 | volatile Object value; 93 | volatile Node next; 94 | } 95 | static class Index { 96 | final Node node; 97 | final Index down; 98 | volatile Index right; 99 | } 100 | 101 | static final class HeadIndex extends Index { 102 | final int level; 103 | HeadIndex(Node node, Index down, Index right, int level) { 104 | super(node, down, right); 105 | this.level = level; 106 | } 107 | } 108 | ``` -------------------------------------------------------------------------------- /linux/VMware如何配置静态IP.md: -------------------------------------------------------------------------------- 1 | 2 | ### 概述 3 | 4 | >这篇文章教你如何配置VMware虚拟机中的静态IP。 5 | 6 | #### 设置虚拟机的网络 7 | 8 | ![在这里插入图片描述](https://img-blog.csdnimg.cn/20190313211146376.?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzI1NDg0MTQ3,size_16,color_FFFFFF,t_70) 9 | 10 | ![在这里插入图片描述](https://img-blog.csdnimg.cn/20190313211259986.?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzI1NDg0MTQ3,size_16,color_FFFFFF,t_70) 11 | ![在这里插入图片描述](https://img-blog.csdnimg.cn/20190313211345298.?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzI1NDg0MTQ3,size_16,color_FFFFFF,t_70) 12 | 13 | >然后点击确定就行了。 14 | 15 | #### 修改VMnet8的IPV4的相关配置 16 | 17 | ![在这里插入图片描述](https://img-blog.csdnimg.cn/20190313211450534.?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzI1NDg0MTQ3,size_16,color_FFFFFF,t_70) 18 | 19 | ![在这里插入图片描述](https://img-blog.csdnimg.cn/20190313211909247.?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzI1NDg0MTQ3,size_16,color_FFFFFF,t_70) 20 | 21 | 22 | #### 设置虚拟机的IP 23 | 24 | >vim /etc/sysconfig/network-scripts/ifcfg-ens33 25 | 26 | 27 | ```text 28 | YPE="Ethernet" 29 | PROXY_METHOD="none" 30 | BROWSER_ONLY="no" 31 | BOOTPROTO="static" 32 | DEFROUTE="yes" 33 | IPV4_FAILURE_FATAL="no" 34 | IPV6INIT="yes" 35 | IPV6_AUTOCONF="yes" 36 | IPV6_DEFROUTE="yes" 37 | IPV6_FAILURE_FATAL="no" 38 | IPV6_ADDR_GEN_MODE="stable-privacy" 39 | NAME="ens33" 40 | UUID="1718c2cf-3d7a-4198-af6d-a77835737e19" 41 | DEVICE="ens33" 42 | ONBOOT="yes" 43 | IPADDR=192.168.192.130 #这个IP地址和网关一个网段即可,192.168.192.* 44 | PREFIX=24 45 | GATEWAY=192.168.192.128 #网关,和我们第二步配置中的相同 46 | NETMASK=255.255.255.0 #子网掩码,和我们第二步配置中的相同 47 | ``` 48 | 49 | >然后更新配置: service network restart 50 | 51 | 52 | ![在这里插入图片描述](https://img-blog.csdnimg.cn/20190313212249789.?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzI1NDg0MTQ3,size_16,color_FFFFFF,t_70) 53 | 54 | 55 | 56 | #### 结果 57 | ![在这里插入图片描述](https://img-blog.csdnimg.cn/20190313212311527.?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzI1NDg0MTQ3,size_16,color_FFFFFF,t_70) 58 | 59 | 60 | #### 备注 61 | 原始博客地址:https://www.cnblogs.com/wangmingshun/p/7708688.html -------------------------------------------------------------------------------- /markdown/markdown--table样式.md: -------------------------------------------------------------------------------- 1 | ```html 2 | 32 | ``` -------------------------------------------------------------------------------- /mysql/mysql--InnoDB.md: -------------------------------------------------------------------------------- 1 | 34 | 35 | 36 | 37 | ### 事务的四种特性 38 | 39 | |特性 |解释说明| 实现| 40 | |--|--|--| 41 | | 原子性 | 原子性也就是说这组语句要么全部执行,要么全部不执行,如果事务执行到一半出现错误,数据库就要回滚到事务开始执行的地方。 |主要是基于MySQ日志系统的redo和undo机制。事务是一组SQL语句,里面有选择,查询、删除等功能。每条语句执行会有一个节点。例如,删除语句执行后,在事务中有个记录保存下来,这个记录中储存了我们什么时候做了什么事。如果出错了,就会回滚到原来的位置,redo里面已经存储了我做过什么事了,然后逆向执行一遍就可以了。 | 42 | | 一致性 | 事务开始前和结束后,数据库的完整性约束没有被破坏。 | | 43 | | 隔离性 |同一时间,只允许一个事务请求同一数据,不同的事务之间彼此没有任何干扰; | | 44 | | 持久性 | 事务完成后,事务对数据库的所有更新将被保存到数据库,不能回滚 | | 45 | 46 | 47 | ### 脏读、不可重复读、幻读 48 | 49 | |名称 |解释| 50 | | --| --| 51 | |脏读 | 所谓的脏读,其实就是读到了别的事务回滚前的脏数据。比如事务B执行过程中修改了数据X,在未提交前,事务A读取了X,而事务B却回滚了,这样事务A就形成了脏读。 | 52 | |不可重复读 |事务A首先读取了一条数据,然后执行逻辑的时候,事务B将这条数据改变了,然后事务A再次读取的时候,发现数据不匹配了,就是所谓的不可重复读了。 | 53 | |幻读 | 事务A首先根据条件索引得到N条数据,然后事务B改变了这N条数据之外的M条或者增添了M条符合事务A搜索条件的数据,导致事务A再次搜索发现有N+M条数据了,就产生了幻读。 | 54 | 55 | 56 | ### 事务隔离级别 57 | 58 | 59 | | 隔离级别| 脏读 |不可重复读 |幻读 | 60 | |--|--|--|--| 61 | |Read uncommitted(读未提交) | × |× | × | 62 | | Read committed(读已提交) | √ |× | × | 63 | | Repeatable read(可重复读)| √ | √ | × | 64 | |、Serializable(串行化) | √ | √ | √ | 65 | 66 | 67 | ### InnoDB中的锁 68 | 69 | >在InnoDB中有三种锁,分别是共享锁(读锁)、独占锁(写锁)、行级锁。 70 | 71 | 72 | >行级锁定最大的特点就是锁定对象的颗粒度很小,也是目前各大数据库管理软件所实现的锁定颗粒度最小的。由于锁定颗粒度很小,所以发生锁定资源争用的概率也最小,能够给予应用程序尽可能大的并发处理能力而提高一些需要高并发应用系统的整体性能。 73 | 虽然能够在并发处理能力上面有较大的优势,但是行级锁定也因此带来了不少弊端。由于锁定资源的颗粒度很小,所以每次获取锁和释放锁需要做的事情也更多,带来的消耗自然也就更大了。此外,行级锁定也最容易发生死锁。 74 | 使用行级锁定的主要是InnoDB存储引擎。 75 | 76 | 77 | >InnoDB的锁定机制和Oracle数据库有不少相似之处。InnoDB的行级锁定同样分为两种类型,共享锁和排他锁,而在锁定机制的实现过程中为了让行级锁定和表级锁定共存,InnoDB也同样使用了意向锁(表级锁定)的概念,也就有了意向共享锁和意向排他锁这两种。 78 | 79 | 80 | >当一个事务需要给自己需要的某个资源加锁的时候,如果遇到一个共享锁正锁定着自己需要的资源的时候,自己可以再加一个共享锁,不过不能加排他锁。但是,如果遇到自己需要锁定的资源已经被一个排他锁占有之后,则只能等待该锁定释放资源之后自己才能获取锁定资源并添加自己的锁定。 81 | 82 | 83 | >而意向锁的作用就是当一个事务在需要获取资源锁定的时候,如果遇到自己需要的资源已经被排他锁占用的时候,该事务可以需要锁定行的表上面添加一个合适的意向锁。如果自己需要一个共享锁,那么就在表上面添加一个意向共享锁。而如果自己需要的是某行(或者某些行)上面添加一个排他锁的话,则先在表上面添加一个意向排他锁。 84 | 85 | >意向共享锁可以同时并存多个,但是意向排他锁同时只能有一个存在。所以,可以说InnoDB的锁定模式实际上可以分为四种:共享锁(S),排他锁(X),意向共享锁(IS)和意向排他锁(IX),我们可以通过以下表格来总结上面这四种所的共存逻辑关系: 86 | 87 | 88 | ![在这里插入图片描述](https://img-blog.csdnimg.cn/20190314214650351.) 89 | 90 | 91 | >如果一个事务请求的锁模式与当前的锁兼容,InnoDB就将请求的锁授予该事务;反之,如果两者不兼容,该事务就要等待锁释放。 92 | 意向锁是InnoDB自动加的,不需用户干预。对于UPDATE、DELETE和INSERT语句,InnoDB会自动给涉及数据集加排他锁(X);对于普通SELECT语句,InnoDB不会加任何锁;事务可以通过以下语句显示给记录集加共享锁或排他锁。 93 | 94 | ```mysql 95 | 共享锁(S):SELECT * FROM table_name WHERE ... LOCK IN SHARE MODE 96 | 排他锁(X):SELECT * FROM table_name WHERE ... FOR UPDATE 97 | ``` 98 | 99 | ### InnoDB行锁的实现方式 100 | 101 | >InnoDB行锁是通过给索引上的索引项加锁来实现的,只有通过索引条件检索数据,InnoDB才使用行级锁,否则,InnoDB将使用表锁 102 | 在实际应用中,要特别注意InnoDB行锁的这一特性,不然的话,可能导致大量的锁冲突,从而影响并发性能。下面通过一些实际例子来加以说明。 103 | 104 | 105 | ```text 106 | (1)在不通过索引条件查询的时候,InnoDB确实使用的是表锁,而不是行锁。 107 | (2)由于MySQL的行锁是针对索引加的锁,不是针对记录加的锁,所以虽然是访问不同行的记录,但是如果是使用相 108 | 同的索引键,是会出现锁冲突的。 109 | (3)当表有多个索引的时候,不同的事务可以使用不同的索引锁定不同的行,另外,不论是使用主键索引、唯一索 110 | 引或普通索引,InnoDB都会使用行锁来对数据加锁。 111 | (4)即便在条件中使用了索引字段,但是否使用索引来检索数据是由MySQL通过判断不同执行计划的代价来决定的, 112 | 如果MySQL认为全表扫描效率更高,比如对一些很小的表,它就不会使用索引,这种情况下InnoDB将使用表锁,而不是 113 | 行锁。因此,在分析锁冲突时,别忘了检查SQL的执行计划,以确认是否真正使用了索引。 114 | ``` 115 | 116 | ### 间隙锁 117 | 118 | >当我们用范围条件而不是相等条件检索数据,并请求共享或排他锁时,InnoDB会给符合条件的已有数据记录的索引项加锁; 119 | 对于键值在条件范围内但并不存在的记录,叫做“间隙(GAP)”,InnoDB也会对这个“间隙”加锁,这种锁机制就是所谓的间隙锁(Next-Key锁) 120 | 假如emp表中只有101条记录,其empid的值分别是 1,2,...,100,101,下面的SQL: 121 | ```mysql 122 | 123 | mysql> select * from emp where empid > 100 for update; 124 | ``` 125 | >是一个范围条件的检索,InnoDB不仅会对符合条件的empid值为101的记录加锁,也会对empid大于101(这些记录并不存在)的“间隙”加锁。 126 | 127 | #### 目的 128 | 129 | >防止幻读,以满足相关隔离级别的要求。对于上面的例子,要是不使用间隙锁,如果其他事务插入了empid大于100的任何记录,那么本事务如果再次执行上述语句,就会发生幻读; 130 | 131 | >为了满足其恢复和复制的需要。很显然,在使用范围条件检索并锁定记录时,即使某些不存在的键值也会被无辜的锁定,而造成在锁定的时候无法插入锁定键值范围内的任何数据。在某些场景下这可能会对性能造成很大的危害。 132 | 133 | ### 死锁 134 | 135 | >在InnoDB中,除单个SQL组成的事务外,锁是逐步获得的,当两个事务都需要获得对方持有的排他锁才能继续完成事务,这种循环锁等待就是典型的死锁。 136 | 137 | >在InnoDB的事务管理和锁定机制中,有专门检测死锁的机制,会在系统中产生死锁之后的很短时间内就检测到该死锁的存在。当InnoDB检测到系统中产生了死锁之后,InnoDB会通过相应的判断来选这产生死锁的两个事务中较小的事务来回滚,而让另外一个较大的事务成功完成。 138 | 139 | 140 | >那InnoDB是以什么来为标准判定事务的大小的呢?MySQL官方手册中也提到了这个问题,实际上在InnoDB发现死锁之后,会计算出两个事务各自插入、更新或者删除的数据量来判定两个事务的大小。也就是说哪个事务所改变的记录条数越多,在死锁中就越不会被回滚掉。 141 | 142 | 143 | >但是有一点需要注意的就是,当产生死锁的场景中涉及到不止InnoDB存储引擎的时候,InnoDB是没办法检测到该死锁的,这时候就只能通过锁定超时限制参数InnoDB_lock_wait_timeout来解决。 144 | 145 | -------------------------------------------------------------------------------- /mysql/mysql--MyISAM.md: -------------------------------------------------------------------------------- 1 | 2 | ### MyISAM中的表级锁 3 | 4 | >MyISAM表的读操作与写操作之间,以及写操作之间是串行的。当一个线程获得对一个表的写锁后,只有持有锁的线程可以对表进行更新操作。其他线程的读、写操作都会等待,直到锁被释放为止。 5 | 6 | >如何加锁:MyISAM在执行查询语句(SELECT)前,会自动给涉及的所有表加读锁,在执行更新操作(UPDATE、DELETE、INSERT等)前,会自动给涉及的表加写锁,这个过程并不需要用户干预,因此,用户一般不需要直接用LOCK TABLE命令给MyISAM表显式加锁。 7 | 8 | 9 | >对于MyISAM存储引擎,虽然使用表级锁定在锁定实现的过程中比实现行级锁定或者页级锁所带来的附加成本都要小,锁定本身所消耗的资源也是最少。但是由于锁定的颗粒度比较大,所以造成锁定资源的争用情况也会比其他的锁定级别都要多,从而在较大程度上会降低并发处理能力。所以,在优化MyISAM存储引擎锁定问题的时候,最关键的就是如何让其提高并发度。由于锁定级别是不可能改变的了,所以我们首先需要尽可能让锁定的时间变短,然后就是让可能并发进行的操作尽可能的并发。 10 | 11 | #### MyISAM表锁优化建议 12 | 13 | ##### 1.查询表级锁争用情况。 14 | 15 | ```mysql 16 | 17 | mysql> show status like 'table%'; 18 | +----------------------------+---------+ 19 | | Variable_name | Value | 20 | +----------------------------+---------+ 21 | | Table_locks_immediate | 100 | 22 | | Table_locks_waited | 11 | 23 | +----------------------------+---------+ 24 | Table_locks_immediate:产生表级锁定的次数; 25 | Table_locks_waited:出现表级锁定争用而发生等待的次数; 26 | ``` 27 | 28 | >两个状态值都是从系统启动后开始记录,出现一次对应的事件则数量加1。如果这里的Table_locks_waited状态值比较高,那么说明系统中表级锁定争用现象比较严重,就需要进一步分析为什么会有较多的锁定资源争用了。 29 | 30 | 31 | 32 | ##### 2.缩短锁定时间 33 | 34 | ```text 35 | a)尽两减少大的复杂Query,将复杂Query分拆成几个小的Query分布进行; 36 | b)尽可能的建立足够高效的索引,让数据检索更迅速; 37 | c)尽量让MyISAM存储引擎的表只存放必要的信息,控制字段类型; 38 | d)利用合适的机会优化MyISAM表数据文件。 39 | ``` 40 | 41 | ##### 3.分离并行的操作 42 | >说到MyISAM的表锁,而且是读写互相阻塞的表锁,可能有些人会认为在MyISAM存储引擎的表上就只能是完全的串行化,没办法再并行了。大家不要忘记了,MyISAM的存储引擎还有一个非常有用的特性,那就是ConcurrentInsert(并发插入)的特性。MyISAM存储引擎有一个控制是否打开Concurrent Insert功能的参数选项:concurrent_insert,可以设置为0,1或者2。三个值的具体说明如下: 43 | 44 | ```text 45 | concurrent_insert=2,无论MyISAM表中有没有空洞,都允许在表尾并发插入记录; 46 | concurrent_insert=1,如果MyISAM表中没有空洞(即表的中间没有被删除的行),MyISAM允许在一个进程读表的同时,另一个进程从表尾插入记录。这也是MySQL的默认设置; 47 | concurrent_insert=0,不允许并发插入。 48 | ``` 49 | >可以利用MyISAM存储引擎的并发插入特性,来解决应用中对同一表查询和插入的锁争用。例如,将concurrent_insert系统变量设为2,总是允许并发插入;同时,通过定期在系统空闲时段执行OPTIMIZE TABLE语句来整理空间碎片,收回因删除记录而产生的中间空洞。 50 | 51 | ##### 合理利用读写优先级 52 | 53 | >这是因为MySQL的表级锁定对于读和写是有不同优先级设定的,默认情况下是写优先级要大于读优先级。 54 | 所以,如果我们可以根据各自系统环境的差异决定读与写的优先级: 55 | 通过执行命令SET LOW_PRIORITY_UPDATES=1,使该连接读比写的优先级高。如果我们的系统是一个以读为主,可以设置此参数,如果以写为主,则不用设置; 56 | 通过指定INSERT、UPDATE、DELETE语句的LOW_PRIORITY属性,降低该语句的优先级。 57 | 虽然上面方法都是要么更新优先,要么查询优先的方法,但还是可以用其来解决查询相对重要的应用(如用户登录系统)中,读锁等待严重的问题。 58 | 另外,MySQL也提供了一种折中的办法来调节读写冲突,即给系统参数max_write_lock_count设置一个合适的值,当一个表的写锁达到这个值后,MySQL就暂时将写请求的优先级降低,给读进程一定获得锁的机会。 59 | 60 | 61 | -------------------------------------------------------------------------------- /mysql/mysql--MySQL慢日志.md: -------------------------------------------------------------------------------- 1 | # MySQL慢查询日志 2 | 3 | >    MySQL有一种日志,叫做慢查询日志,主要就是用来记录一些耗时的查询操作。通过这个日志我们就可以分析出哪些的操作是影响性能的,我们需要对其进行一些优化措施。 4 | 5 | # 如何查看是否开启 6 | 7 | ```sql 8 | show variables like '%quer%'; 9 | ``` 10 | 11 | ![](https://github.com/wuxiaobo000111/pictures/blob/master/2019-03-17/8.jpg?raw=true "") 12 | 13 | 14 | >    我的这个是没有开启的,如果想要开启,则就需要指定如下配置: 15 | ```properties 16 | #指定开启慢查询日志 17 | slow-query-log=1 18 | #指定慢查询日志的路径 19 | slow_query_log_file="mysql-slow.log" 20 | #指定查询时间大于多少的才进行记录,但是是毫秒,也就是操作大于 10ms 的操作都会被记录。 21 | long_query_time=10 22 | ``` 23 | >    修改过后可以看出如下所示: 24 | 25 | ![](https://github.com/wuxiaobo000111/pictures/blob/master/2019-03-17/9.jpg?raw=true "") 26 | -------------------------------------------------------------------------------- /mysql/mysql--mysql cluster.md: -------------------------------------------------------------------------------- 1 | # MySQL Cluster 概述 2 | 3 | >    MySQL Cluster技术在分布式系统中为MySQL提供了冗余特性,增强了安全性,可以大大的提高系统的可靠性和数据的有效性。MySQL集群需要一组计算机,每台计算机可以理解为一个节点,这些节点的功能各不相同。MySQL Cluster按照功能来分,可以分为三种节点:管理节点、数据节点和SQL节点。集群中的某台计算机可以是某一个节点,也可以是两种或者三种节点的集合,这些节点组合在一起,为应用提供具有高可靠性、高性能的Cluster数据管理。 4 | 5 | 6 | >    MySQL Cluster简单地讲是一种MySQL集群的技术,是由一组计算机构成,每台计算机可以存放一个或者多个节点,其中包括MySQL服务器,DNB Cluster的数据节点,管理其他节点,以及专门的数据访问程序,这些节点组合在一起,就可以为应用提高可高性能、高可用性和可缩放性的Cluster数据管理; 7 | 8 |   9 | 10 | >    MySQL Cluster的访问过程大致是这样的,应用通常使用一定的负载均衡算法将对数据访问分散到不同的SQL节点,SQL节点对数据节点进行数据访问并从数据节点返回数据结果,管理节点仅仅只是对SQL节点和数据节点进行配置管理; 11 | 12 |   13 | # 节点 14 | 15 | ## 管理节点 16 | >    管理节点主要是用来对其他的节点进行管理。通常通过配置config.ini文件来配置集群中有多少需要维护的副本、配置每个数据节点上为数据和索引分配多少内存、IP地址、以及在每个数据节点上保存数据的磁盘路径; 17 | 18 | >     管理节点通常管理Cluster配置文件和Cluster日志。Cluster中的每个节点从管理服务器检索配置信息,并请求确定管理服务器所在位置的方式。如果节点内出现新的事件的时候,节点将这类事件的信息传输到管理服务器,将这类信息写入到Cluster日志中。 19 | > 20 | >    一般在MySQL Cluster体系中至少需要一个管理节点,另外值得注意的是,因为数据节点和SQL节点在启动之前需要读取Cluster的配置信息,所以通常管理节点是最先启动的。 21 | ## SQL节点 22 | 23 | >     SQL节点简单地讲就是mysqld服务器,应用不能直接访问数据节点,只能通过SQL节点访问数据节点来返回数据。任何一个SQL节点都是连接到所有的存储节点的,所以当任何一个存储节点发生故障的时候,SQL节点都可以把请求转移到另一个存储节点执行。通常来讲,SQL节点越多越好,SQL节点越多,分配到每个SQL节点的负载就越小,系统的整体性能就越好; 24 | 25 | ## 数据节点 26 | 27 | >     数据节点用来存放Cluster里面的数据,MySQL Cluster在各个数据节点之间复制数据,任何一个节点发生了故障,始终会有另外的数据节点存储数据。 28 | 29 | 30 | ![最基础的MySQL cluster](https://github.com/wuxiaobo000111/pictures/blob/master/2019-03-17/1.png?raw=true "") 31 | 32 | >    努力了一天,也没有实现上述这个cluster,主要遇到的问题是: 33 | 34 | ![](https://github.com/wuxiaobo000111/pictures/blob/master/2019-03-17/7.jpg?raw=true "") 35 | 36 | >    基本上试了百度上所有的方法,一直没有解决...... 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /mysql/mysql--未解决的问题.md: -------------------------------------------------------------------------------- 1 | ### 问题一:聚簇索引索引和非聚簇索引在何时情况下使用。 -------------------------------------------------------------------------------- /mysql/mysql--索引类型.md: -------------------------------------------------------------------------------- 1 | ### 概述 2 | 3 | >    这里讲述一下mysql中的索引类型,以及常见的InnoDB和MyIASM引擎中可以使用的索引。 4 | 5 | ### 索引类型 6 | 7 | #### 主键索引 8 | 9 | >    主键是一种唯一性索引,但它必须指定为“PRIMARY KEY”。如果你曾经用过AUTO_INCREMENT类型的列,你可能已经熟悉主键之类的概念了。主键一般在创建表的时候指定,例如“CREATE TABLE tablename ( [...], PRIMARY KEY (列的列表) ); ”。但是,我们也可以通过修改表的方式加入主键,例如“ALTER TABLE tablename ADD PRIMARY KEY (列的列表); ”。每个表只能有一个主键。 10 | 11 | #### 唯一索引 12 | >    唯一索引,即是唯一的意思,在数据库表结构中对字段添加唯一索引后进行数据库进行存储操作时数据库会判断库中是否已经存在此数据,不存在此数据时才能进行插入操作。 13 | 14 | ##### 唯一索引与唯一约束的区别 15 | 16 | >    在mysql中貌似唯一约束就是唯一索引,并没有什么不同,可能叫法不同,在sqlserver中区分还是挺明确的。 17 | 18 | >    博客中的一句话说的很在理,你为了做到数据不能有重复值,但是数据库怎么保证没有重复值呢?当然是在存储数据的时候查一遍,那么怎样查找快呢? 当然是创建索引,所以,在创建唯一约束的时候就创建了唯一索引。这可能也是mysql的一个优化机制。 19 | 20 | ```mysql 21 | 添加索引 22 | alter table table_name add unique(column) 23 | 删除索引 24 | alter table table_name drop index colum_name 25 | ``` 26 | 27 | >    注意:唯一索引中的值可以有一个为null,但是主键索引并不能为null。 28 | 29 | #### 普通索引 30 | 31 | ```mysql 32 | 第一种方式 : 33 | 34 | CREATE INDEX account_Index ON `award`(`account`); 35 | 第二种方式: 36 | 37 | ALTER TABLE award ADD INDEX account_Index(`account`) 38 | ``` 39 | 40 | 41 | #### 复合索引 42 | 43 | 44 | #### 全文索引 45 | 46 | >    即为全文索引,目前只有MyISAM引擎支持。其可以在CREATE TABLE ,ALTER TABLE ,CREATE INDEX 使用,不过目前只有 CHAR、VARCHAR ,TEXT 列上可以创建全文索引。 47 | 48 | 49 | #### 聚簇索引 50 | 51 | >    Innodb的聚簇索引在同一个B-Tree中保存了索引列和具体的数据,在聚簇索引中,实际的数据保存在叶子页中,中间的节点页保存指向下一层页面的指针。“聚簇”的意思是数据行被按照一定顺序一个个紧密地排列在一起存储。一个表只能有一个聚簇索引,因为在一个表中数据的存放方式只有一种。 52 | 53 | ![在这里插入图片描述](https://img-blog.csdnimg.cn/20190315143608702.?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzI1NDg0MTQ3,size_16,color_FFFFFF,t_70) 54 | 55 | >    这是个B+ 树的结构,关于B+ 树会在之后的内容中讲到。这里只是说明在InnoDB中,索引是使用B+ 树的结构,在这个结构中,只有叶子节点才会存在节点数组,其他的只是会存放索引值。 56 | 57 | ##### 聚簇索引的优缺点 58 | 59 | >优点:
    聚簇索引将索引和数据行保存在同一个B-Tree中,查询通过聚簇索引可以直接获取数据,相比非聚簇索引需要第二次查询(非覆盖索引的情况下)效率要高。 60 |     聚簇索引对于范围查询的效率很高,因为其数据是按照大小排列的, 61 | 62 | 63 | >缺点: 64 |     1.聚簇索引的更新代价比较高,如果更新了行的聚簇索引列,就需要将数据移动到相应的位置。这可能因为要插入的页已满而导致“页分裂”。 65 |     2.插入速度严重依赖于插入顺序,按照主键进行插入的速度是加载数据到Innodb中的最快方式。如果不是按照主键插入,最好在加载完成后使用OPTIMIZE TABLE命令重新组织一下表。 66 |     3.聚簇索引在插入新行和更新主键时,可能导致“页分裂”问题。 67 |     4.聚簇索引可能导致全表扫描速度变慢,因为可能需要加载物理上相隔较远的页到内存中(需要耗时的磁盘寻道操作)。 68 | 69 | 70 | #### 非聚簇索引 71 | 72 | >    非聚簇索引,又叫二级索引。二级索引的叶子节点中保存的不是指向行的物理指针,而是行的主键值。当通过二级索引查找行,存储引擎需要在二级索引中找到相应的叶子节点,获得行的主键值,然后使用主键去聚簇索引中查找数据行,这需要两次B-Tree查找。 73 | 74 | ![在这里插入图片描述](https://img-blog.csdnimg.cn/20190315145432234.) 75 | 76 | 77 | 78 | #### 聚簇索引和非聚簇索引的使用场景 79 | 80 | 81 | 82 | #### InnoDB使用的索引结构(B+树,注意:是叫B树) 83 | [B-树](https://github.com/wuxiaobo000111/markdown/blob/master/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84/B%E6%A0%91.md "B-树") 84 | 85 | [B+树](https://github.com/wuxiaobo000111/markdown/blob/master/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84/B%2B%E6%A0%91.md "B+树") 86 | 87 | >    B-树和B+树的原理都在如上的两篇文章中。 88 | 89 | 90 | #### mysql索引失效 91 | 92 | >     不想重复写了,这里献上我的一个笔记的链接地址:http://note.youdao.com/noteshare?id=5b32c54e21d5af4f1107665e173a7989&sub=D1FBD8FA38564234A049A48D8A1BAD12 -------------------------------------------------------------------------------- /mysql/mysql中涉及的技术.md: -------------------------------------------------------------------------------- 1 | ### mysql 学习中的要点 2 | 3 | ``` 4 | 1.mysql存储引擎 5 | 2.mysql索引 6 | 3.msyql索引失效 7 | 4.explain 8 | 5.tidb 9 | 6.mycat 10 | 7.索引原理 11 | 8.性能优化 12 | 9.shardingsphere 13 | ``` -------------------------------------------------------------------------------- /mysql/mysql中的搜索引擎.md: -------------------------------------------------------------------------------- 1 | 31 | ### 概述 32 | 33 | >这里只是讲解一下MySQL中常用的两种存储引擎,InnoDB和MyISAM。 34 | 35 | >MyISAM是MySQL的默认数据库引擎(5.5版之前),由早期的ISAM(Indexed Sequential Access Method:有索引的顺序访问方法)所改良。虽然性能极佳,但却有一个缺点:不支持事务处理(transaction)。 36 | 37 | 38 | ### 两者之间的区别 39 | 40 | | 特性 |MyISAM | InnoDB | 41 | |--| --| --| 42 | |事务支持 |MyISAM不支持事务, InnoDB的AUTOCOMMIT默认是打开的,即每条SQL语句会默认被封装成一个事务,自动提交,这样会影响速度,所以最好是把多条SQL语句显示放在begin和commit之间,组成一个事务去提交。
MyISAM是非事务安全型的,默认开启自动提交,宜合并事务,一同提交,减小数据库多次提交导致的开销,大大提高性能。| InnoDB支持事务,而且是事务安全的 43 | | 存储结构 | MyISAM:每个MyISAM在磁盘上存储成三个文件。第一个文件的名字以表的名字开始,扩展名指出文件类型。.frm文件存储表定义。数据文件的扩展名为.MYD (MYData)。索引文件的扩展名是.MYI (MYIndex)。 | InnoDB:所有的表都保存在同一个数据文件中(也可能是多个文件,或者是独立的表空间文件),InnoDB表的大小只受限于操作系统文件的大小,一般为2GB。| 44 | | 存储空间 |MyISAM:可被压缩,存储空间较小。支持三种不同的存储格式:静态表(默认,但是注意数据末尾不能有空格,会被去掉)、动态表、压缩表。 | InnoDB:需要更多的内存和存储,它会在主内存中建立其专用的缓冲池用于高速缓冲数据和索引。 | 45 | | 可移植性、备份和恢复 |MyISAM:数据是以文件的形式存储,所以在跨平台的数据转移中会很方便。在备份和恢复时可单独针对某个表进行操作。 |InnoDB:免费的方案可以是拷贝数据文件、备份 binlog,或者用 mysqldump,在数据量达到几十G的时候就相对痛苦了。 | 46 | |事务支持 | 可以和其他字段一起建立联合索引。引擎的自动增长列必须是索引,如果是组合索引,自动增长可以不是第一列,他可以根据前面几列进行排序后递增。| InnoDB中必须包含只有该字段的索引。引擎的自动增长列必须是索引,如果是组合索引也必须是组合索引的第一列。| 47 | | 锁表差异 |只支持表级锁,用户在操作myisam表时,select,update,delete,insert语句都会给表自动加锁,如果加锁以后的表满足insert并发的情况下,可以在表的尾部插入新的数据。
MyISAM同一个表上的读锁和写锁是互斥的,MyISAM并发读写时如果等待队列中既有读请求又有写请求,默认写请求的优先级高,即使读请求先到,所以MyISAM不适合于有大量查询和修改并存的情况,那样查询进程会长时间阻塞。因为MyISAM是锁表,所以某项读操作比较耗时会使其他写进程饿死。| 支持事务和行级锁,是innodb的最大特色。行锁大幅度提高了多用户并发操作的新能。但是InnoDB的行锁,只是在WHERE的主键是有效的,非主键的WHERE都会锁全表的。 | 48 | |全文索引 |支持(FULLTEXT类型的)全文索引 | 不支持(FULLTEXT类型的)全文索引,但是innodb可以使用sphinx插件支持全文索引,并且效果更好。 | 49 | | 表主键 |允许没有任何索引和主键的表存在,索引都是保存行的地址 | 如果没有设定主键或者非空唯一索引,就会自动生成一个6字节的主键(用户不可见),数据是主索引的一部分,附加索引保存的是主索引的值。InnoDB的主键范围更大,最大是MyISAM的2倍。 | 50 | | 表的具体行数 | 保存有表的总行数,如果select count(*) from table;会直接取出出该值。 | 没有保存表的总行数(只能遍历),如果使用select count(*) from table;就会遍历整个表,消耗相当大,但是在加了wehre条件后,myisam和innodb处理的方式都一样。 | 51 | | CURD操作| 如果执行大量的SELECT,MyISAM是更好的选择。 | 如果你的数据执行大量的INSERT或UPDATE,出于性能方面的考虑,应该使用InnoDB表。DELETE 从性能上InnoDB更优,但DELETE FROM table时,InnoDB不会重新建立表,而是一行一行的删除,在innodb上如果要清空保存有大量数据的表,最好使用truncate table这个命令。 | 52 | |外键 | 不支持 |支持 | 53 | |查询效率 | 。如果应用中需要执行大量的SELECT查询,那么MyISAM是更好的选择| 如果应用中需要执行大量的INSERT或UPDATE操作,则应该使用InnoDB,这样可以提高多用户并发操作的性能。 | 54 | -------------------------------------------------------------------------------- /mysql/悲观锁和乐观锁的实现.md: -------------------------------------------------------------------------------- 1 | >原博客地址:https://blog.csdn.net/xz0125pr/article/details/51698507 2 | 3 | 4 | 5 | ### 悲观锁 6 | 7 | >悲观锁,正如其名,它指的是对数据被外界(包括本系统当前的其他事务,以及来自外部系统的事务处理)修改持保守态度,因此,在整个数据处理过程中,将数据处于锁定状态。悲观锁的实现,往往依靠数据库提供的锁机制(也只有数据库层提供的锁机制才能真正保证数据访问的排他性,否则,即使在本系统中实现了加锁机制,也无法保证外部系统不会修改数据)。 8 | 9 | 10 | ### 举例 11 | 12 | >商品goods表中有一个字段status,status为1代表商品未被下单,status为2代表商品已经被下单,那么我们对某个商品下单时必须确保该商品status为1。假设商品的id为1。 13 | 14 | >如果不加锁的代码如下所示: 15 | ```mysql 16 | //1.查询出商品信息 17 | 18 | select status from t_goods where id=1; 19 | 20 | //2.根据商品信息生成订单 21 | 22 | insert into t_orders (id,goods_id) values (null,1); 23 | 24 | //3.修改商品status为2 25 | 26 | update t_goods set status=2; 27 | ``` 28 | 29 | 30 | >上面这种场景在高并发访问的情况下很可能会出现问题。前面已经提到,只有当goods status为1时才能对该商品下单,上面第一步操作中,查询出来的商品status为1。但是当我们执行第三步Update操作的时候,有可能出现其他人先一步对商品下单把goods status修改为2了,但是我们并不知道数据已经被修改了,这样就可能造成同一个商品被下单2次,使得数据不一致。所以说这种方式是不安全的。 31 | 32 | 33 | #### 使用悲观锁 34 | 35 | >在上面的场景中,商品信息从查询出来到修改,中间有一个处理订单的过程,使用悲观锁的原理就是,当我们在查询出goods信息后就把当前的数据锁定,直到我们修改完毕后再解锁。那么在这个过程中,因为goods被锁定了,就不会出现有第三者来对其进行修改了。 36 | 37 | >要使用悲观锁,我们必须关闭mysql数据库的自动提交属性,因为MySQL默认使用autocommit模式,也就是说,当你执行一个更新操作后,MySQL会立刻将结果进行提交。 38 | 39 | ```mysql 40 | 我们可以使用命令设置MySQL为非autocommit模式: 41 | 42 | set autocommit=0; 43 | 44 | 45 | 46 | 设置完autocommit后,我们就可以执行我们的正常业务了。具体如下: 47 | 48 | //0.开始事务 49 | 50 | begin;/begin work;/start transaction; (三者选一就可以) 51 | 52 | //1.查询出商品信息 53 | 54 | select status from t_goods where id=1 for update; 55 | 56 | //2.根据商品信息生成订单 57 | 58 | insert into t_orders (id,goods_id) values (null,1); 59 | 60 | //3.修改商品status为2 61 | 62 | update t_goods set status=2; 63 | 64 | //4.提交事务 65 | 66 | commit;/commit work; 67 | ``` 68 | 69 | >与普通查询不一样的是,我们使用了select…for update的方式,这样就通过数据库实现了悲观锁。此时在t_goods表中,id为1的 那条数据就被我们锁定了,其它的事务必须等本次事务提交之后才能执行。这样我们可以保证当前的数据不会被其它事务修改。 70 | 71 | 72 | >需要注意的是,在事务中,只有SELECT ... FOR UPDATE 或LOCK IN SHARE MODE 同一笔数据时会等待其它事务结束后才执行,一般SELECT ... 则不受此影响。拿上面的实例来说,当我执行select status from t_goods where id=1 for update;后。我在另外的事务中如果再次执行select status from t_goods where id=1 for update;则第二个事务会一直等待第一个事务的提交,此时第二个查询处于阻塞的状态,但是如果我是在第二个事务中执行select status from t_goods where id=1;则能正常查询出数据,不会受第一个事务的影响。 73 | 74 | 75 | ### 乐观锁 76 | 77 | >乐观锁( Optimistic Locking ) 相对悲观锁而言,乐观锁假设认为数据一般情况下不会造成冲突,所以在数据进行提交更新的时候,才会正式对数据的冲突与否进行检测,如果发现冲突了,则让返回用户错误的信息,让用户决定如何去做。那么我们如何实现乐观锁呢,一般来说有以下2种方式: 78 | 79 | >1.使用数据版本(Version)记录机制实现,这是乐观锁最常用的一种实现方式。何谓数据版本?即为数据增加一个版本标识,一般是通过为数据库表增加一个数字类型的 “version” 字段来实现。当读取数据时,将version字段的值一同读出,数据每更新一次,对此version值加一。当我们提交更新的时候,判断数据库表对应记录的当前版本信息与第一次取出来的version值进行比对,如果数据库表当前版本号与第一次取出来的version值相等,则予以更新,否则认为是过期数据。用下面的一张图来说明: 80 | 81 | 82 | ![在这里插入图片描述](https://img-blog.csdnimg.cn/20190314222418526.?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzI1NDg0MTQ3,size_16,color_FFFFFF,t_70) 83 | 84 | 85 | >如上图所示,如果更新操作顺序执行,则数据的版本(version)依次递增,不会产生冲突。但是如果发生有不同的业务操作对同一版本的数据进行修改,那么,先提交的操作(图中B)会把数据version更新为2,当A在B之后提交更新时发现数据的version已经被修改了,那么A的更新操作会失败。 86 | 87 | 88 | >2.乐观锁定的第二种实现方式和第一种差不多,同样是在需要乐观锁控制的table中增加一个字段,名称无所谓,字段类型使用时间戳(timestamp), 和上面的version类似,也是在更新提交的时候检查当前数据库中数据的时间戳和自己更新前取到的时间戳进行对比,如果一致则OK,否则就是版本冲突。 89 | 90 | 91 | ### 思考一个问题 92 | >如果一个表是主键自增的,那么这个表新增的时候是行锁还是表锁。 93 | 94 | 95 | >这个时候会使用的是表锁,也叫作自增锁,自增锁是一种特殊的表级别锁(table-level lock),专门针对事务插入AUTO_INCREMENT类型的列。最简单的情况,如果一个事务正在往表中插入记录,所有其他事务的插入必须等待,以便第一个事务插入的行,是连续的主键值。 96 | -------------------------------------------------------------------------------- /redis/Bitmpas.md: -------------------------------------------------------------------------------- 1 | # Bitmaps简介 2 | 3 | >    redis为Bitmaps提供了一套单独的命令。可以想象为Bitmaps是一个已位为单位的数组,数组中的每个元素只能存储0和1,数组中的下标在Bitmaps叫偏移量。其实本身还是用字符串类进行存储。 4 | 5 | # 命令 6 | 7 | ```redis 8 | setbit key offset value 9 | 10 | 设置键的第offset个位上的值。 11 | 12 | getbit key offset 13 | 14 | 获取键的第offset的值 15 | 16 | bitcount key [start][end] 17 | 18 | 获取Bitmaps指定范围值为1的个数(start和end表示起始和结束字节数) 19 | 20 | bitop 运算符 destkey key[key......] 21 | 22 | 可以做多个Bitmaps的and(交集),or(并集),not(非),xor(异或)然后将结果保存在destkey中。 23 | 24 | bitpos key targetBit [start][end] 25 | 26 | 27 | 计算Bitmaps中的第一个值为targetBit的偏移量。 28 | ``` -------------------------------------------------------------------------------- /redis/HyperLogLog.md: -------------------------------------------------------------------------------- 1 | # 概述 2 | 3 | >    HyperLogLog是一种基数算法,通过HyperLogLog可以利用很小的内存空间完成独立总数的统计。命令如下所示: 4 | 5 | ```redis 6 | pfadd key element[element] 7 | 8 | 9 | pfcount kye[key] 10 | 11 | pfmerge [key][key] 12 | 13 | 注意:HyperLogLog统计总数的时候可能存在误差。 14 | ``` -------------------------------------------------------------------------------- /redis/PipeLine.md: -------------------------------------------------------------------------------- 1 | # 什么是redis中的Pipeline 2 | 3 | >    将一组Redis命令进行组装,通过一次RTT传输给Redis,再将这组Redis的执行结果按照顺序返回给客户端。 4 | 5 | ![在这里插入图片描述](https://github.com/wuxiaobo000111/pictures/blob/master/2019-03-29/5.jpg?raw=true) 6 | -------------------------------------------------------------------------------- /redis/redis--Hash.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # 散列(hash) 4 | 5 | >    一个散列由多个域值对(field-value pair)组成,散列的域和值都可以是文字、整数、浮点或者二进制数据。 6 | >    同一个散列里面的每个域必须是独一无二、各不相同 的,而域的值则没有这一要求,换句话说,不同域的值 可以是重复的。 7 | >    通过命令,用户可以对散列执行设置域值对、获取域的值、检查域是否存在等操作,也可以让 Redis 返回散列包含的所有域、所有值或者所有域值对。 8 | 9 | # 基本操作 10 | ``` 11 | HSET key field value 12 | 在散列键 key 中关联给定的域值对 field 和 value 。 13 | 如果域 field 之前没有关联值,那么命令返回 1 ; 14 | 如果域 field 已经有关联值,那么命令用新值覆盖旧值,并返回 0。 15 | 复杂度为 O(1) 。 16 | HGET key field 17 | 返回散列键 key 中,域 field 所关联的值。如果域 field 没有关联值,那么返回 nil 。 18 | 复杂度为 O(1) 。、 19 | HSETNX key field value 20 | 如果散列键 key 中,域 field 不存在(也即是, 还没有与之相关联的值),那么关联给定的域值对 field 和value 。 21 | 如果域 field 已经有与之相关联的值,那么命令不做动作。 22 | 复杂度为 O(1) 。 23 | HEXISTS key field 24 | 查看散列键 key 中,给定域 field 是否存在:存在返回 1 ,不存在返回 0 。 25 | 复杂度为 O(1) 。 26 | HDEL key field [field ...] 27 | 删除散列键 key 中的一个或多个指定域,以及那些域的值。 28 | 不存在的域将被忽略。命令返回被成功删除的域值对数量。 29 | 复杂度为 O(N) ,N 为被删除的域值对数量。 30 | HLEN key 31 | 返回散列键 key 包含的域值对数量。 32 | 复杂度为 O(1) 。 33 | ``` 34 | # 批量操作 35 | ``` 36 | HMSET key field value [field value ...] 37 | 在散列键 key 中关联多个域值对,相当于同时执行多个 HSET 。复杂度为O(N) ,N 为输入的域值对数量。 38 | HMGET key field [field ...] 39 | 返回散列键 key 中,一个或多个域的值,相当于同时执行多个 HGET 。复杂度O(N) , N 为输入的域数量。 40 | HKEYS key 41 | 返回散列键 key 包含的所有域。复杂度为O(N),N 为被返回域的数量。 42 | HVALS key 43 | 返回散列键 key 中,所有域的值。复杂度为O(N),N 为被返回值的数量。 44 | HGETALL key 45 | 返回散列键 key 包含的所有域值对。复杂度为O(N),N 为被返回域值对的数量。 46 | ``` 47 | # 数字操作 48 | ``` 49 | HINCRBY key field increment 50 | 为散列键 key 中,域 field 的值加上整数增量 increment 。复杂度为O(1) 51 | HINCRBYFLOAT key field increment 52 | 为散列键 key 中,域 field 的值加上浮点数增量 increment 。复杂度为O(1) 53 | ``` 54 | 55 | >    虽然 Redis 没有提供与以上两个命令相匹配的 HDECRBY 命令和 HDECRBYFLOAT 命令,但我们同样可以通过将 increment 设为负数来达到做减法的效果。hash类型和string类型的比较 56 | 57 | # 使用hash的好处 58 | >    将数据放到同一个地方 59 | 散列可以让我们将一些相关的信息储存在同一个地方,而不是直接分散地储存在整个数据库里面,这不仅方便了数据管理,还可以尽量避免误操作发生。避免键名冲突减少内存的使用 60 | 61 | # 结论 62 | 63 | >    只要有可能的话,就尽量使用散列键而不是字符串键来储存键值对数据,因为散列键管理方便、能够避免键名冲突、并且还能够节约内存。 64 | 一些没办法使用散列键来代替字符串键的情况: 65 | 1. 使用二进制位操作命令:因为 Redis 目前支持对字符串键进行 SETBIT、GETBIT、BITOP 等操作, 66 | 如果你想使用这些操作,那么只能使用字符串键。 67 | 2. 使用过期功能:Redis 的键过期功能目前只能对键进行过期操作,而不能对散列的域进行过期操 68 | 作,因此如果你要对键值对数据使用过期功能的话,那么只能把键值对储存在字符串里面。 69 | 70 | # 内部编码 71 | 72 | >    Hash类型的内部编码有两种 73 | 74 | 75 | ```text 76 | 1.zipList(压缩列表):当Hash类型元素个数小于hash-max-ziplist-entries配置(默认是512个),同时所有值都小于hash-max 77 | -ziplist-value配置(默认是64个字节)时候,redis就会使用ziplist作为哈希的内部实现,ziplist使用更加紧凑的结构实现 78 | 多个元素的连续存储,所以在节省内存方面比hashtable更加优秀。 79 | 80 | 2.hashtable(hash表):当hash类型无法满足ziplist的条件的时候,redis就会使用hashtable作为hash的内部实现,因为此时zip 81 | list的读写效率会下降,而hashtable的读写时间复杂度为O(1)。 82 | ``` 83 | 84 | -------------------------------------------------------------------------------- /redis/redis--list.md: -------------------------------------------------------------------------------- 1 | 2 | # 列表 3 | 4 | >    一个列表项可以包含一个或者多个数量的项,每个项可以按照它们被推入到列表的位置来排列。 5 | >    每个列表项所处的位置都决定了这个项的索引值,索引以0位开始,从列表的左端到右端依次递增,位于列表最左端的项的索引为0,而位于列表最右端的项的索引为N-1,其中N为列表的长度。 6 | >    列表包含的项可以出现重复,它们不必是唯一的。 7 | 8 | # 推入和弹出的操作 9 | ``` 10 | LPUSH key value [value ...] 11 | 将一个或者是多个值依次推入到列表的左端,命名返回新值被推入后,列表目前所包含的项数量。 12 | 复杂度为O(N),其中N为被推入值的数量,如果只有一个值,那么这个命令的复杂度为O(1)。 13 | RPUSH key value [value ...] 14 | 将一个或以上数量的值依次推入到列表的右端,命令返回新值被推入之后,列表目前包含的项数量。 15 | 复杂度为O(N) ,其中 N 为被推入的数量,如果只推入一个值,那么命令的复杂度 O(1) 。 16 | LPOP key 17 | 移除并返回列表最左端的项,复杂度为O(1)。 18 | RPOP key 19 | 移除并返回列表最右端的项,复杂度是O(1)。 20 | ``` 21 | # 长度、索引和范围操作 22 | ``` 23 | LLEN key 24 | 返回列表键 key 长度,也即是,返回列表包含的列表项数量。 25 | 因为Redis 会记录每个列表的长度,所以这个命令无须遍历列表,它的复杂为 O(1)。 26 | LINDEX key index 27 | 返回列表键 key 中,指定索引 index 上的列表项。index 索引可以是正数或者负数。 28 | 复杂度为O(N) ,N 列表的长度。 29 | LRANGE key start stop 30 | 返回列表键key 中,从索引start 至索引stop 范围内的所有列表。两个索引参数都可以是正数或负数。 31 | 复杂度为 O(N) N 为被返回的列表项数量。 32 | ``` 33 | # 插入和删除操作 34 | ``` 35 | LSET key index value 36 | 将列表键key 索引 index 上的列表项设置为value ,设置成功时命令返回 OK 。 37 | 如果 index 参数超过了列表的索引范围,那么命令返回一个错误。 38 | 针对表头和表尾节点进行处理时(index 为 0 或者 -1),命令的复杂度为 O(1) ;其他情况下,命令的复杂度为 O(N) ,N 为列表的长度。 39 | LINSERT key BEFORE|AFTER pivot value 40 | 根据命令调用时传递的是 BEFORE 选项还是 AFTER 选项,将值 value 插入到指定列表项 pivot 的之前或者之后。当 41 | pivot 不存在于列表 key 时,不执行任何操作。返回 -1 pivot 不存在;返回 0 表示键key 不存在;插入成功时则返 42 | 回列表当前的长度。复杂度为O(N) ,N 为列表长度。 43 | LREM key count value 44 | 根据参数 count 的值,移除列表中与参数 value 相等的列表项: 45 | •如果 count > 0 ,那么从表头开始向表尾搜索,移除最多count 个值为value 的列表项。 46 | •如果 count < 0 ,那么从表尾开始向表头搜索,移除最多 abs(count) 个值为value 的列表项。 47 | •如果 count = 0 ,那么移除列表中所有值为 value 的列表项。 48 | 命令返回被移除列表项的数量。命令的复杂度为 O(N) ,N 为列表的长度。 49 | LTRIM key start stop 50 | 对一个列表进行修剪(trim),让列表只保留指定索引范围内的列表项,而将不在范围内的其他列表项全部删除。 51 | 两个索引都可以是正数或者负数。命令执行成功时返回 OK , 52 | 复杂度为 O(N) ,N为被移除列表项的数量。 53 | ``` 54 | # 阻塞式弹出操作 55 | ``` 56 | BLPOP key [key ...] timeout 57 | LPOP 命令的阻塞版本;命令会以从左到右的顺序,访问给定的各个列表,并弹出首个非空列表最左端的项; 58 | 如果所有给定列表都为空,那么客户端将被阻塞,直到等待超时,或者有可弹出的项出现为止;设置参数为 59 | 0 表示永远阻塞。 60 | 复杂度为O(N),N为输入列表的数量。 61 | BRPOP key [key ...] timeout 62 | RPOP 命令的阻塞版本:命令会以从左到右的顺序,访问给定的各个列表,并弹出首个非空列表最右端的项; 63 | 如果所有给定列表都为空,那么客户端将被阻塞,直到等待超时,或者有可弹出的项出现为止;设置 timeout 64 | 参数为 0 表示永远阻塞。 65 | 复杂度为O(N),N为输入列表的数量。 66 | ``` 67 | 68 | # 内部编码 69 | 70 | >    list类型的内部编码有两种 71 | 72 | 73 | ```text 74 | 1.zipList(压缩列表):当Hash类型元素个数小于hash-max-ziplist-entries配置(默认是512个),同时所有值都小于hash-max 75 | -ziplist-value配置(默认是64个字节)时候,redis就会使用ziplist作为哈希的内部实现,ziplist使用更加紧凑的结构实现 76 | 多个元素的连续存储,所以在节省内存方面比hashtable更加优秀。 77 | 78 | 2.linkedlist(链表):当列表类型无法满足ziplist的条件的时候,redis会使用Linkedlist作为列表的内部实现。 79 | 80 | 81 | ``` 82 | 83 | 84 | -------------------------------------------------------------------------------- /redis/redis--string.md: -------------------------------------------------------------------------------- 1 | # 常用命令 2 | 3 | ## 基本操作 4 | 5 | ```text 6 | SET key value 7 | 将字符串键 key 的值设置为 value ,命令返回 OK 表示设置成功。 8 | 如果字符串键 key 已经存在,那么用新值覆盖原来的旧值。 9 | 复杂度为 O(1) 。 10 | GET key 11 | 返回字符串键 key 储存的值。 12 | 复杂度为 O(1) 。 13 | SETNX key value 14 | 仅在键 key 不存在的情况下,将键 key 的值设置为 value ,效果和 SET key value NX 一样。 15 | NX 的意思为“Not eXists”(不存在)。 16 | 键不存在并且设置成功时,命令返回 1;因为键已经存在而导致设置失败时,命令返回 0 。 17 | MSET key value [key value ...] 18 | 一次为一个或多个字符串键设置值,效果和同时执行多个SET 命令一样。命令返回 OK 。 19 | 复杂度为O(N),N 为要设置的字符串键数量。 20 | MGET key [key ...] 21 | 一次返回一个或多个字符串键的值,效果和同时执行多GET 命令一样。 22 | 复杂度O(N),N 为要获取的字符串键数量。 23 | MSETNX key value [key value ...] 24 | 只有在所有给定键都不存在的情况下, MSETNX 会为所有给定键设置值,效果和同时执行多个 25 | SETNX 一样。如果给定的键至少有一个是存在的,那么 MSETNX 将不执行任何设置操作。 26 | 返回 1 表示设置成功,返回 0 表示设置失败。复杂度为 O(N) , N 为给定的键数量。 27 | GETSET key new-value 28 | 将字符串键的值设置为 new-value ,并返回字符串键在设置新值之前储存的旧值(old value)。 29 | 复杂度为 O(1) 。 30 | APPEND key value 31 | 将值 value 推入到字符串键 key 已储存内容的末尾。 32 | O(N), 其中 N 为被推入值的长度。 33 | STRLEN key 34 | 返回字符串键 key 储存的值的长度。 35 | 因为 Redis 会记录每个字符串值的长度,所以获取该值的复杂度为 O(1) 。 36 | 37 | ``` 38 | ## 索引和范围 39 | ``` 40 | 字符串的索引( index)以 0 为开始,从字符串的开头向字符串的结尾依次递增,字符串第一个字符的索 41 | 引为 0 ,字符串最后一个字符的索引为 N-1 ,其中 N 为字符串的长度。 42 | 除了(正数)索引之外,字符串还有负数索引:负数索引以 -1 为开始,从字符串的结尾向字符串的开头 43 | 依次递减,字符串的最后一个字符的索引为 -N ,其中 N 为字符串的长度。 44 | SETRANGE key index value 45 | 从索引 index 开始,用 value 覆写(overwrite)给定键 key 所储存的字符串值。只接受正数索引。 46 | 命令返回覆写之后,字符串值的长度。复杂度为 O(N), N 为 value 的长度。 47 | GETRANGE key start end 48 | 返回键 key 储存的字符串值中,位于 start 和 end 两个索引之间的内容(闭区间,start 和 end 会被包括 49 | 在内)。和 SETRANGE 只接受正数索引不同, GETRANGE 的索引可以是正数或者负数。 50 | 复杂度为 O(N) , N 为被选中内容的长度。 51 | ``` 52 | 53 | 54 | ## 数字操作 55 | ``` 56 | 只要储存在字符串键里面的值可以被解释为 64 位整数,或者 IEEE-754 标准的 64 位浮点数, 57 | 那么用户就可以对这个字符串键执行针对数字值的命令。 58 | INCRBY key increment 59 | 将 key 所储存的值加上增量 increment ,命令返回操作执行之后,键 key 的当前值。复杂度为O(1) 60 | DECRBY key decrement 61 | 将 key 所储存的值减去减量 decrement ,命令返回操作执行之后,键 key的当前值。复杂度为O(1) 62 | INCR key 63 | 等同于执行 INCRBY key 1 复杂度为O(1) 64 | DECR key 65 | 等同于执行 DECRBY key 1 复杂度为O(1) 66 | INCRBYFLOAT key increment 67 | 为字符串键 key 储存的值加上浮点数增量 increment ,命令返回操作执行之后,键 key 的值。 68 | 没有相应的 DECRBYFLOAT ,但可以通过给定负值来达到 DECRBYFLOAT 的效果。 69 | 复杂度为 O(1) 。 70 | 注意事项 71 | 72 | 即使字符串键储存的是数字值,它也可以执行 APPEND、STRLEN、SETRANGE 和 GETRANGE 。 73 | 当用户针对一个数字值执行这些命令的时候,Redis 会先将数字值转换为字符串,然后再执行命令。 74 | ``` 75 | 76 | ## 二进制数据操作 77 | ``` 78 | SET 、GET 、SETNX、APPEND等命令同样可以用于设置二进制数据。 79 | 和储存文字时一样,字符串键在储存二进制位时,索引也是从 0 开始的。 80 | 但是和储存文字时,索引从左到右依次递增不同,当字符串键储存的是二进制位时,二进制位的索引会 81 | 从左到右依次递减。 82 | SETBIT key index value 83 | 将给定索引上的二进制位的值设置为 value ,命令返回被设置的位原来储存的旧值。 84 | 复杂度为 O(1) 。 85 | GETBIT key index 86 | 返回给定索引上的二进制位的值。 87 | 复杂度为 O(1) 。 88 | BITCOUNT key [start] [end] 89 | 计算并返回字符串键储存的值中,被设置为 1 的二进制位的数量。 90 | 一般情况下,给定的整个字符串键都会进行计数操作, 但通过指定额外的 start 或 end 参数,可以让计 91 | 数只在特定索引范围的位上进行。 92 | start 和 end 参数的设置和 GETRANGE 命令类似,都可以使用负数值:比如 -1 表示最后一个位,而 -2 93 | 表示倒数第二个位,以此类推。 94 | BITOP operation destkey key [key ...] 95 | 对一个或多个保存二进制位的字符串键执行位元操作,并将结果保存到 destkey 上。 96 | operation 可以是 AND 、 OR 、 NOT 、 XOR 这四种操作中的任意一种: 97 | 命令 98 | ``` 99 | 100 | # 内部编码 101 | 102 | >    string类型的内部编码有三种 103 | 104 | ``` 105 | int : 8个字节的长整型 106 | embstr : 小于等于39个字节的字符串 107 | raw : 大于39个字节的字符串 108 | ``` -------------------------------------------------------------------------------- /redis/redis--zset.md: -------------------------------------------------------------------------------- 1 | 2 | # Zset类型 3 | 4 | >    有序集合和集合一样,都可以包含任意数量的、各不相同的元素( element),不同于集合的是,有序集合的每个元素都关联着一个浮点数格式的分 值(score),并且有序集合会按照分 值,以从小到大的顺序来排列有序集合中的各个元素。 5 | 6 | >    虽然有序集合中的每个元素都必 须是各不相同的,但元素的分 值并没有这一限制,换句话来说,两个不同元素的分值可以是相同的。 7 | 8 | # 基本操作 9 | ``` 10 | ZADD key score element [[score element] [score element] ...] 11 | 按照给定的分值和元素,将任意数量的元素添加到有序集合里面,命令的返回 值为成功添加的元素数量。 12 | 复杂度 O(M*log(N)),其中 N 为有序集合已有的元素数量, M 为成功添加的新元素数量。 13 | ZREM key element [element ...] 14 | 从有序集合中删除指定的元素,以及这些元素关联的分值,命令返回被成功删除的元素数量。 15 | 复杂度 O(M*log(N)),其中 N 为有序集合已有的元素数量, M 为成功删除的元素数量。 16 | ZSCORE key element 17 | 返回有序集合中,指定元素的分值。 18 | 复杂度为 O(1) 。 19 | ZINCRBY key increment element 20 | 为有序集合指定元素的分 值加上增量 increment ,命令返回执行操作之后,元素的分 值。 21 | 没有相应的 ZDECRBY 命令,但可以通过将 increment 设置为负数来减少分值。 22 | 复杂度为 O(log(N)) 。 23 | ZCARD key 24 | 返回有序集合包含的元素数量(基数)。复 杂度为 O(1) 。 25 | ZRANK key element 26 | 返回指定元素在有序集合中的排名,其中 排名按照元素的分值从小到大计算。 27 | 复杂度为 O(log(N)) 。 28 | ZREVRANK key member 29 | 返回成员在有序集合中的逆序排名,其中 排名按照元素的分值从大到小计算。 30 | 排名以 0 开始。 31 | 复杂度为 O(log(N)) 。 32 | ``` 33 | # 分值范围操作 34 | ``` 35 | ZRANGE key start stop [WITHSCORES] 36 | 返回有序集合在按照分值从小到大排列元素(升序排列) 的情况下,索引 start 至索引 stop 范围之内的所有元素。 37 | 两个索引都可以是正数或者 负数。当给定 WITHSCORES 选项时,命令会将元素和分值一并返回。 38 | 命令的复杂度为 O(log(N)+M) ,N 为有序集合的基数,而 M 则为被返回元素的数量。 39 | ZREVRANGE key start stop [WITHSCORES] 40 | 返回有序集合在按照分值从大到小排列元素(降序排列) 的情况下,索引 start 至索引 stop 范围之内的所有元素。 41 | 两个索引都可以是正数或者 负数。 42 | 当给定 WITHSCORES 选项时,命令会把元素和分值一并返回。 43 | 命令的复杂度为 O(log(N)+M) ,N 为有序集合的基数,而 M 则为被返回元素的数量。 44 | ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT offset count] 45 | 返回有序集合在按照分值升序排列元素的情况下,分值在 min 和 max 范围之内的所有元素。 46 | 给定 WITHSCORES 选项时 47 | ,元素和分值会一并返回。给定 LIMIT 选项时,可以通过 offset 参数指定返 48 | 回的结果集要跳过多少个元素,而 count参数则用于指定返回的元素数量。 49 | 命令的复杂度为 O(log(N)+M), N 为有序集合的基数,而 M 则为被返回元素的数量。 50 | ZREVRANGEBYSCORE key min max [WITHSCORES] [LIMIT offset count] 51 | 返回有序集合在按照分值降序排列元素的情况下,分值在 min 和 max 范围之内的所有元素。 52 | 给定 WITHSCORES 选项时,元素和分值会一并返回。给定 LIMIT 选项, 53 | 可以通过 offset 参数指定返回的结果集要跳过多少个元素,而 count 54 | 参数则用于指定返回的元素数量。 55 | 命令的复杂度为 O(log(N)+M), N 为有序集合的基数,而 M 则为被返回元素的数量。 56 | ZCOUNT key min max 57 | 返回有序集合在升序排列元素的情况下,分 值在 min 和 max 范围内的元素数量。 58 | 命令的复杂度为 O(log(N)), N 为有序集合的基数。 59 | ZREMRANGEBYRANK key start stop 60 | 移除有序集合中,元素按升序 进行排列的情况下,指定排名范 围内的所有元素。 61 | 命令的复杂度为 O(log(N)+M), N 为有序集合的基数,而 M 则为被移除元素的数量。 62 | ZREMRANGEBYSCORE key min max 63 | 移除有序集合中,分值范围介于 min 和 max 之内的所有元素。 64 | 命令的复杂度为 O(log(N)+M),其中 N 为有序集合的基数,而 M 为被移除元素的数量。 65 | ``` 66 | # 集合的运算操作 67 | ``` 68 | ZUNIONSTORE destkey numkeys key [key ...] 69 | 计算并集 复杂度为O(N)+O(M log(M)), N 为参与并集计算的元素数量, M 为结果集的基数。 70 | ZINTERSTORE destkey numkeys key [key ...] 71 | 计算交集 复杂度为O(N*K)+O(M*log(M)), N 为给定有序集合中,基数最小的有序集合的基数, 72 | K 为给定有序集合的数量, M 为结果集的基数。 73 | ``` 74 | 75 | # 内部编码 76 | >    有序集合类型的内部编码有两种。 77 | 78 | ``` 79 | 1.zipList(压缩列表):当有序集合类型元素个数小于hash-max-ziplist-entries配置(默认是512个),同时所有值都小于hash-max 80 | -ziplist-value配置(默认是64个字节)时候,redis就会使用ziplist作为有序集合类型的内部实现,ziplist可以有效减少 81 | 内存的使用 82 | 83 | 2.skiplist(跳跃表):当有序集合类型无法满足ziplist的条件的时候,redis会使用skiplist作为有序集合的内部实现。这个时候ziplist 84 | 的读写效率会下降。 85 | ``` -------------------------------------------------------------------------------- /redis/redis--发布订阅.md: -------------------------------------------------------------------------------- 1 | ![在这里插入图片描述](https://github.com/wuxiaobo000111/pictures/blob/master/2019-03-31/2.jpg?raw=true) 2 | 3 | 4 | >    上图介绍了发布订阅模式的模型。 5 | 6 | 7 | # 发布订阅命令 8 | 9 | 10 | 11 | ```redis 12 | publish channelname message 13 | 14 | 向channelname中发送消息 15 | 16 | subscribe channelname [channelname] 17 | 18 | 订阅多个频道 19 | unsubscribe channelname[channelname] 20 | 21 | 取消订阅 22 | 23 | psubscribe pattern [pattern] 24 | 25 | 按照模式订阅 26 | 27 | punsubscribe pattern [pattern] 28 | 29 | 按照模式取消订阅 30 | 31 | 32 | pubsub channels [pattern] 33 | 34 | 查看活跃的频道 35 | 36 | pubsub numsub [channel .......] 37 | 38 | 查看频道订阅数 39 | 40 | pubsub numpat 41 | 查看模式订阅数 42 | ``` 43 | 44 | ![在这里插入图片描述](https://github.com/wuxiaobo000111/pictures/blob/master/2019-03-31/1.jpg?raw=true) 45 | 46 | 47 | ![在这里插入图片描述](https://github.com/wuxiaobo000111/pictures/blob/master/2019-03-31/3.jpg?raw=true) 48 | 49 | >    需要注意一下几点 50 | 51 | ```text 52 | 1. 客户端在执行订阅命令之后就进入了订阅状态,只能接受subscribe、psubscribe、unsubscribe、punsubscribe命令。 53 | 54 | 2.新开启的订阅客户端,无法收到该频道之前的消息,因为redis不会对发布的消息进行持久化。 55 | ``` -------------------------------------------------------------------------------- /redis/redis-benchmark详解.md: -------------------------------------------------------------------------------- 1 | # 概述 2 | 3 | >    可以为redis做基准性能测试。 4 | 5 | ```redis 6 | -c 7 | -c(clients)表示客户端的并发量(默认是50) 8 | 9 | -n 10 | 11 | -n(num)选项代表客户端的请求总数 12 | 13 | ---redis-benchmark -c 100 -n 20000---表示100客户端同时请求,一共请求20000次 。 14 | 15 | -q 16 | 17 | redis-benchmark的requests per senond信息 18 | 19 | -r 向redis中插入更多的随机键 20 | 21 | ---redis-benchmark -c 100 -n 20000 -r 10000--- 22 | -r 10000表示只会对后四位做随机处理 23 | -p 24 | 25 | 每个请求pipeline的数据量(默认是1) 26 | 27 | -k 28 | 29 | -k表示客户端是否使用keepalive 1表示使用,0表示不使用,默认值是1. 30 | -t 31 | 32 | 对指定命令记性基准测试 33 | 34 | --csv 35 | 36 | 会将结果按照csv格式输出 37 | 38 | ``` 39 | 40 | ![在这里插入图片描述](https://github.com/wuxiaobo000111/pictures/blob/master/2019-03-29/4.jpg?raw=true) 41 | -------------------------------------------------------------------------------- /redis/redis-cli详解.md: -------------------------------------------------------------------------------- 1 | # 概述 2 | 3 | >    这里说明一下redisc-cli的具体命令 4 | 5 | ```redis 6 | -r times: 代表这个命令将会times多次 7 | ``` 8 | 9 | ![在这里插入图片描述](https://github.com/wuxiaobo000111/pictures/blob/master/2019-03-26/18.jpg?raw=true) 10 | 11 | 12 | ```redis 13 | -i seconds :每隔几秒就会执行一次(必须和-r一起使用,同时不支持毫秒级别) 14 | ``` 15 | ![在这里插入图片描述](https://github.com/wuxiaobo000111/pictures/blob/master/2019-03-26/19.jpg?raw=true) 16 | 17 | 18 | ```redis 19 | -x :从标准输入读取数据作为redis-cli的最后一个参数。 20 | ``` 21 | ![在这里插入图片描述](https://github.com/wuxiaobo000111/pictures/blob/master/2019-03-26/20.jpg?raw=true) 22 | 23 | 24 | >    演示将输入world,设置key为hello的value是world。 25 | 26 | ```redis 27 | -c: 连接redis cluster 节点的时候需要使用 28 | ``` 29 | 30 | ```redis 31 | -a: 如果配置了密码,可以通过-a直接输入密码 32 | ``` 33 | 34 | ```redis 35 | --scan和--pattern : 用于扫描指定模式的键,相当于使用scan命令 36 | ``` 37 | 38 | ![在这里插入图片描述](https://github.com/wuxiaobo000111/pictures/blob/master/2019-03-26/21.jpg?raw=true) 39 | 40 | 41 | >    匹配所有的key是以name11111开头。 42 | 43 | 44 | ```redis 45 | --slave: 把当前客户端模拟成为当前redis节点的从节点,可以用来获取当前redis节点的更新操作。 46 | ``` 47 | 48 | ```redis 49 | --rdb:请求redis实例生成并发送RDB持久化文件,保存到本地。 50 | 51 | ``` 52 | 53 | ![在这里插入图片描述](https://github.com/wuxiaobo000111/pictures/blob/master/2019-03-26/22.jpg?raw=true) 54 | 55 | >    将rdb文件写入到/usr/local/redis/bin/dump1.rdb文件中。 56 | 57 | ```redis 58 | --pipe:将命令封装成为Redis通信协议定义的数据格式,批量发送给redis执行。 59 | ``` 60 | 61 | 62 | ```redis 63 | --bigkeys:使用scan命令对redis进行采样,从中找到内存占用比较大的键值对。这些键值对可能是系统的瓶颈。 64 | ``` 65 | 66 | ```redis 67 | 68 | --eval:执行指定的Lua脚本。 69 | ``` 70 | 71 | ```redis 72 | 73 | --stat: 可以实时获取redis的重要统计信息 74 | 75 | ``` 76 | 77 | ![在这里插入图片描述](https://github.com/wuxiaobo000111/pictures/blob/master/2019-03-26/23.jpg?raw=true) 78 | 79 | ```redis 80 | --raw 返回格式化之后的结果。 81 | --no-raw 要求命令返回的是原始数据 82 | ``` 83 | 84 | ![在这里插入图片描述](https://github.com/wuxiaobo000111/pictures/blob/master/2019-03-26/24.jpg?raw=true) 85 | -------------------------------------------------------------------------------- /redis/redis-server详解.md: -------------------------------------------------------------------------------- 1 | # redis-server 2 | 3 | >    redis-server除了启动redis之外,还有一个--test-memory选项。可以用来检测当前系统能否稳定的分配指定容量的内存给redis -------------------------------------------------------------------------------- /redis/redis-set.md: -------------------------------------------------------------------------------- 1 | 2 | # set 3 | 4 | >    Redis 的集合以无序的方式储存多个各不相同的元素。用户可以快速地向集合添加元素,或者从集合里面 删除元素,也可以对多个集合进行集合运算操作,比 5 | 如计算并集、交集和差集。 6 | 7 | # 元素操作 8 | ``` 9 | SADD key element [element ...] 10 | 将一个或多个元素添加到 给定的集合里面,已经存在于集合的元素会自 动被忽略,命令返回新添加到集合的元素数量。 11 | 命令的复杂度为 O(N),N 为成功添加的元素数量。 12 | SREM key element [element ...] 13 | 移除集合中的一个或者多个元素,不存在于集合中的元素会自 动被忽略,命令返回存在并且被移除的元素数量。 14 | 命令的复杂度为 O(N),N 为被移除元素的数量。 15 | SISMEMBER key element 16 | 检查给定的元素是否存在于集合,存在的 话返回 1 ;如果元素不存在,或者 给定的键不存在,那么返回 0 。 17 | 命令的复杂度为 O(1) 。 18 | SCARD key 19 | 返回集合包含的元素数量(也即是集合的基数)。 20 | 因为 Redis 会储存集合的长度,所以命令的复杂度为 O(1) 。 21 | SMEMBERS key 22 | 返回集合包含的所有元素。 23 | 命令的复杂度为 O(N) ,N 为集合的大小。当集合的基数比较大时,执行这个命令有可能会㐀成服 务器阻塞,将来会介 24 | 绍更好的方式来迭代集合中的元素。 25 | SPOP key 26 | 随机地从集合中移除并返回一个元素,复 杂度为 O(1) 。 27 | SRANDMEMBER key [count] 28 | 如果没有给定可选的 count 参数,那么命令随机地返回集合中的一个元素。 29 | 如果给定了 count 参数,那么: 30 | • 当 count 为正数,并且少于集合基数 时,命令返回一个包含 count 个元素的数组,数组中的每个元 31 | 素各不相同。如果 count 大于或等于集合基数,那么命令返回整个集合。 32 | • 当 count 为负数时,命令返回一个数组,数组中的元素可能会重复出现多次,而数组的长度为 33 | count 的绝对值。 34 | 和 SPOP 不同, SRANDMEMBER 不会移除被返回的元素。命令的复杂度为 O(N),N 为被返回元素的数量。 35 | ``` 36 | # 集合运算操作 37 | ``` 38 | SDIFF key [key ...] 39 | 计算所有给定集合的差集,并返回结果。 40 | 复杂度为O(N),N 为所有参与差集计算的元素数量之和。 41 | SDIFFSTORE destkey key [key ...] 】 42 | 计算所有给定集合的差集,并将结果储存到 destkey 。 43 | 复杂度是O(N),N 为所有参与差集计算的元素数量之和。 44 | SINTER key [key ...] 45 | 计算所有给定集合的交集,并返回结果。 46 | 复杂度为O(N * M), N 为给定集合当中基数最小的集合, M 为给定集合的个数。 47 | SINTERSTORE destkey key [key ...] 48 | 计算所有给定集合的交集,并将结果储存到 destkey 。 49 | 复杂度为O(N * M), N 为给定集合当中基数最小的集合, M 为给定集合的个数。 50 | SUNION key [key ...] 51 | 计算所有给定集合的并集,并返回结果。 52 | 复杂度为O(N),N 为所有参与计算的元素数量。 53 | SUNIONSTORE destkey key [key ...] 54 | 计算所有给定集合的并集,并将结果储存到 destkey 。 55 | 复杂度为O(N),N 为所有参与计算的元素数量。 56 | ``` 57 | 58 | # 内部编码 59 | 60 | >     集合类型的内部编码有两种: 61 | 62 | 63 | ```text 64 | 1.intset(整数集合),当集合中的元素都是整数并且元素个数小于set-max-intset-entries配置(默认是512个)的时候, 65 | redis就会选用intset来作为集合的内部实现,从而减少内存的使用。 66 | 67 | 2.hashtable(hash表),当集合类型无法满足intset的条件的时候,redis就会使用hashtable作为集合的内部实现。 68 | 69 | ``` -------------------------------------------------------------------------------- /redis/主从复制/主从复制中读写分离带来的问题.md: -------------------------------------------------------------------------------- 1 | 2 | # 概述 3 | 4 | >    当redis使用读写分离的时候可能会带来如下问题:复制数据延迟,读到过期数据和从节点故障。 5 | 6 | # 复制数据延迟 7 | 8 | # 读到过期数据 9 | 10 | >当主节点存储大量设置超时的数据的时候,如缓存数据,Redis内部需要维护过期数据删除策略。删除策略主要有两种方式:惰性删除和定时删除。 11 | 12 | ```text 13 | 1. 惰性删除:主节点每次处理读取命令的时候,都会检查键是否超时,如果超时则执行del命令删除键对象,之后del命令 14 | 也会异步发送给从节点。从节点从来不会主动删除超时数据。 15 | 2. 定时删除:Redis主节点在内部定时任务会循环采样一定数据的键,当发现采样的键时候执行del命令,之后再次通 16 | 过给从节点。 17 | 18 | 上述两种方式如下图所示: 19 | 20 | ``` 21 | 22 | 23 | ![在这里插入图片描述](https://github.com/wuxiaobo000111/pictures/blob/master/2019-04-09/2.jpg?raw=true) 24 | 25 | 26 | 27 | ![在这里插入图片描述](https://github.com/wuxiaobo000111/pictures/blob/master/2019-04-09/3.jpg?raw=true) 28 | 29 | >如果此时数据大量超时,主节点的采样速度跟不上过期速度并且主节点并没有读取过期键的操作,那么从节点无法接受del命令。这个时候还是会在从节点上读到超时的数据。可以通过升级Redis的版本来解决这个问题。 30 | 31 | # 从节点故障问题 32 | 33 | >需在在客户端维护可用从节点列表,当从节点故障的时候可以立即切换到其他从节点或者是主节点上。 -------------------------------------------------------------------------------- /redis/主从复制/主从复制的原理.md: -------------------------------------------------------------------------------- 1 | # 引入 2 | 3 | >    这里深入讲一下redis复制的原理。 4 | 5 | # 复制过程 6 | 7 | 8 | >    先上个图。 9 | 10 | ![在这里插入图片描述](https://github.com/wuxiaobo000111/pictures/blob/master/2019-04-08/7.jpg?raw=true) 11 | 12 | ```text 13 | 1.保存主节点信息 14 | 从节点执行slaveof命令之后保存主节点信息就返回了。建立复制流程还没有开始。 15 | 2.主从建立socket连接 16 | 从节点内部通过每秒圆形的定时任务维护复制相关逻辑,当定时任务发现存在新的主节点后,会尝试与该节点建立网络连接。 17 | 3. 发送ping命令 18 | 检测主从知之间socket是否可用。 19 | 检测主节点当前是否可以接受处理命令。 20 | 如果网络超时或者是无法响应命令,从节点会断开复制连接,下次定时任务会发起重连。 21 | 4.权限验证 22 | 如果主节点设置了requirepass参数,则需要密码验证, 23 | 从节点必须配置masterauth参数保证与从节点相同的密码才能通过验证。 24 | 如果验证失败复制将终止,从节点重新发起复制流程。 25 | 5.同步数据集 26 | 对于首次建立复制,从节点会把持有的所有数据都发送给从节点。同步分成为两种:全量同步和部分同步。 27 | 6.命令持续复制 28 | 当主节点把当前的数据同步给从节点之后,就完成了复制了建立流程。接下来主节点会持续的把写命令发 29 | 送给从节点。保证主从数据一致性。 30 | ``` 31 | 32 | # 数据同步的方式 33 | 34 | >    redis在2.8版本之后采用了psync命令完成主从数据同步。同步过程可以分成为两种:全量复制和部分复制。 35 | 36 | ```text 37 | 1.全量复制:一般用于初次复制场景,redis早起支持的复制功能只有全量复制,会把主节点上的数据一次性发送给从节点。 38 | 39 | 2.部分复制:用于处理在主从复制中因为网络闪断原因造成的数据丢失场景。当从节点再次连上主节点之后, 40 | 如果条件允许,主节点会补发丢失数据给从节点. 41 | ``` 42 | 43 | >    我们知道redis使用psync来进行主从复制。那么psync需要以下组件来支持: 44 | ```text 45 | 1.主从节点各自复制偏移量。 46 | 2.主节点复制积压缓冲区。 47 | 3.主节点运行id。 48 | ``` 49 | ## 复制偏移量 50 | 51 | >    主节点会维护master的字节长度和slave的字节长度。 52 | ![在这里插入图片描述](https://github.com/wuxiaobo000111/pictures/blob/master/2019-04-08/8.jpg?raw=true) 53 | 54 | 55 | >    从节点在接受到主节点发送的命令之后,也会累加自身的偏移量。通过对比复制偏移量,可以判断主从节点数据是否一致。 56 | 57 | ![在这里插入图片描述](https://github.com/wuxiaobo000111/pictures/blob/master/2019-04-08/9.jpg?raw=true) 58 | 59 | 60 | ## 复制积压缓冲区 61 | 62 | >    复制积压缓冲区是保存在主节点上的一个固定长度的队列,当主节点有连接的从节点被创建的时候,master不但会响应写命令,不但把命令发送给slave,同时还会写入到复制积压缓冲区。作用是用于部分复制和复制命令丢失的数据补救。 63 | 64 | ![在这里插入图片描述](https://github.com/wuxiaobo000111/pictures/blob/master/2019-04-08/10.jpg?raw=true) 65 | 66 | 67 | ## 主节点运行ID 68 | 69 | >    这里我们需要记住的是redis节点启动的时候会分配一个40位的16进制作为运行ID。这个ID用来表示redis节点。当redis重启的时候,这个ID会发生变化。 70 | 71 | ## psync命令 72 | 73 | >    命令格式: psync{runId}{offset}。runId就是主节点运行时id。offset就是当前从节点已经复制的数据偏移量。 74 | 75 | ![在这里插入图片描述](https://github.com/wuxiaobo000111/pictures/blob/master/2019-04-08/11.jpg?raw=true) 76 | 77 | 78 | ```text 79 | 1.slave发送psync给master。 80 | 81 | 2.master根据psync参数和自身情况返回结果: 82 | 如果回复+FULLRESYNC{runId}{offset},那么从节点将触发全量复制。 83 | 如果回复+CONTINUE:从节点将触发部分复制流程。 84 | 如果回复+ERR,说明无法识别psync命令,从节点将发送旧版的sync命令触发全量复制流程。 85 | ``` -------------------------------------------------------------------------------- /redis/主从复制/主从复制的拓扑结构.md: -------------------------------------------------------------------------------- 1 | # 拓扑结构概述 2 | 3 | >    redis的拓扑结构的复杂性可以分成为三种:一主一从,一主多从,树状主从结构。从工作的角度来讲我们公司基本上采用的是一主多从。了解一下就行了,这里觉得没有深入研究的必要。 4 | -------------------------------------------------------------------------------- /redis/主从复制/如何配置主从.md: -------------------------------------------------------------------------------- 1 | # redis主从复制的三种方式 2 | 3 | >    基础的就不讲了......配置复制的三种方式: 4 | ```text 5 | 1. 在配置文件中加入slaveof{masterHost}{masterPort}随着redis的启动生效。 6 | 2.在redis-server启动命令之后加入--slaveof{masterHost}{masterPort}生效。 7 | 3.直接使用命令:slaveof{masterHost}{masterPort}生效。 8 | ``` 9 | 10 | >    下面我们就使用第三种最简单的方式来创建redis集群吧。我们在同一台机器上模拟创建一个redis集群。首先复制一下redis文件夹 11 | ![在这里插入图片描述](https://github.com/wuxiaobo000111/pictures/blob/master/2019-04-08/1.jpg?raw=true) 12 | 13 | >    然后修改复制之后的redis(也就是从机)修改端口号为6380。 14 | 15 | ![在这里插入图片描述](https://github.com/wuxiaobo000111/pictures/blob/master/2019-04-08/2.jpg?raw=true) 16 | 17 | >    启动主从两台机器。可以通过ps命令查询是否启动成功。 18 | ![在这里插入图片描述](https://github.com/wuxiaobo000111/pictures/blob/master/2019-04-08/3.jpg?raw=true) 19 | 20 | >    访问从机客户端,使用slaveof命令关键主机。 21 | 22 | ![在这里插入图片描述](https://github.com/wuxiaobo000111/pictures/blob/master/2019-04-08/4.jpg?raw=true) 23 | 24 | >    同时启动主从两个客户端。加以验证。 25 | 26 | ![在这里插入图片描述](https://github.com/wuxiaobo000111/pictures/blob/master/2019-04-08/5.jpg?raw=true) 27 | 28 | >    也可以使用info命令查看主从信息。 29 | 30 | ![在这里插入图片描述](https://github.com/wuxiaobo000111/pictures/blob/master/2019-04-08/6.jpg?raw=true) 31 | 32 | 33 | # 断开复制 34 | 35 | >    其实非常简单。在从节点使用slaveof no one命令就可以断开主从复制关系。需要注意的是从节点断开复制后并不会抛弃原来的数据。只是无法再获取主节点上的数据变化。 36 | 37 | >    通过slaveof命令还可以实现切换主的操作。使用slaveof{newMasterIP}{newMasterPost}命令就行。 38 | 39 | # 安全性 40 | 41 | >    一般情况下主节点会设置requirepass参数进行密码验证。这个时候需要丛节点也配置相同的密码才能正确的连接到主节点并发起复制流程。其实这个问题也是看情况,如果是放在云上的机器可以通过设置密码的方法。我其实遇到过好几次redis在云服务器上被拿去挖矿的经历。如果公司内部有本地机房,所有的请求走内网访问,这个时候redis其实可以不设置密码。所以看情况分析吧。 42 | 43 | 44 | # 只读 45 | 46 | >    其实所有的关于数据库基本上都是实现主从同步,读写分离。mysql的主从同步是基于log文件实现的。redis的主从同步是通过快照的方式实现的(具体内容在之后会讲到)。读写分离就是写走主库,读走从库。redis配置也很简单,设置slave-read-only=yes配置就行。 47 | 48 | 49 | # 传输延迟 50 | 51 | >    redis为我们提供了repl-disable-tcp-nodelay参数用于控制是否关闭TCP-NODELAY。默认是关闭。以下是说明: 52 | ```text 53 | 1.当关闭的时候,主节点产生的命令数据无论大小都会发送给从节点。适合于同机或者是同机房部署。 54 | 2.当开启时候,主节点会合并较小的TCP数据从而节省带宽。默认发送时间间隔取决于Linux内核。 55 | ``` 56 | 57 | 58 | -------------------------------------------------------------------------------- /redis/事务和Lua.md: -------------------------------------------------------------------------------- 1 | 2 | # 事务 3 | 4 | ```java 5 | /** 6 | * 测试watch 7 | */ 8 | public static void test1(Jedis jedis) throws InterruptedException { 9 | jedis.watch("watchkey"); 10 | jedis.set("watchkey","false"); 11 | Transaction transaction = jedis.multi(); 12 | transaction.set("watchkey","123"); 13 | transaction.set("company","嘀嗒出行"); 14 | transaction.exec(); 15 | jedis.unwatch(); 16 | String wuxiaobo = jedis.get("company"); 17 | System.out.println(wuxiaobo); 18 | } 19 | 20 | /** 21 | * redis的事务问题 22 | * @param jedis 23 | */ 24 | public static void test0(Jedis jedis) { 25 | //运行这个命令会清空redis数据库,请小心使用 26 | jedis.flushDB(); 27 | //获取redis 28 | Transaction transaction = jedis.multi(); 29 | transaction.set("wuxiaobo","123"); 30 | transaction.set("company","吴晓波公司"); 31 | transaction.exec(); 32 | String wuxiaobo = jedis.get("wuxiaobo"); 33 | System.out.println(wuxiaobo); 34 | } 35 | 36 | 37 | /** 38 | * 当有错误的时候,redis的事务是不执行的 39 | * @param jedis 40 | */ 41 | public static void test(Jedis jedis) { 42 | //运行这个命令会清空redis数据库,请小心使用 43 | jedis.flushDB(); 44 | //获取redis 45 | Transaction transaction = jedis.multi(); 46 | transaction.set("wuxiaobo","123"); 47 | //当有报错的时候,事务是不执行的 48 | int i = 1 / 0; 49 | transaction.set("company","嘀嗒出行"); 50 | transaction.exec(); 51 | String wuxiaobo = jedis.get("wuxiaobo"); 52 | System.out.println(wuxiaobo); 53 | } 54 | ``` 55 | 56 | # redis事务 原理 57 | 58 | >MULTI、EXEC、DISCARD和WATCH命令是Redis事务功能的基础。Redis事务允许在一次单独的步骤中执行一组命令,并且可以保证如下两个重要事项: 59 | 60 | >Redis会将一个事务中的所有命令序列化,然后按顺序执行。Redis不可能在一个Redis事务的执行过程中插入执行另一个客户端发出的请求。这样便能保证Redis将这些命令作为一个单独的隔离操作执行。 61 | 62 | >在一个Redis事务中,Redis要么执行其中的所有命令,要么什么都不执行。因此,Redis事务能够保证原子性。 63 | 64 | >case例子 65 | ``` 66 | WATCH state 67 | value = GET state; 68 | if value == 1 69 | UNWATCH state 70 | Return false; 71 | MULTI 72 | SET state 1 73 | result = EXEC 74 | if result == success 75 | return true; 76 | return false; 77 | ``` 78 | 79 | >MULTI:用于标记事务块的开始。Redis会将后续的命令逐个放入队列中,然后才能使用EXEC命令原子化地执行这个命令序列。 80 | >EXEC:在一个事务中执行所有先前放入队列的命令,然后恢复正常的连接状态。 81 | 当使用WATCH命令时,只有当受监控的键没有被修改时,EXEC命令才会执行事务中的命令,这种方式利用了检查再设置(CAS)的机制。 82 | 83 | >DISCARD:清除所有先前在一个事务中放入队列的命令,然后恢复正常的连接状态。如果使用了WATCH命令,那么DISCARD命令就会将当前连接监控的所有键取消监控。 84 | 85 | >WATCH:当某个事务需要按条件执行时,就要使用这个命令将给定的键设置为受监控的。 86 | 87 | > UNWATCH 88 | 清除所有先前为一个事务监控的键。如果你调用了EXEC或DISCARD命令,那么就不需要手动调用UNWATCH命令。 89 | 90 | # Lua(尚未学习) 91 | -------------------------------------------------------------------------------- /redis/哨兵/redis哨兵配置说明.md: -------------------------------------------------------------------------------- 1 | 2 | # sentinel monitor 3 | 4 | ```text 5 | sentinel monitor 6 | 7 | master-name是一个别名。 8 | quorum表示判定主节点不可达所需要的票数。 9 | 10 | 这里没有配置从节点的信息,是因为从节点的信息可以通过主节点的信息获取到。 11 | ``` 12 | 13 | # sentinel down-after-milliseconds 14 | 15 | ```text 16 | sentinel down-after-milliseconds 17 | 18 | 1.Sentinel节点都要通过定期发送ping命令来判断redis数据节点和其他Sentinel节点是否可达。如果超过了 19 | down-after-milliseconds,则判断节点不可达。 20 | 21 | ``` 22 | 23 | 24 | # sentinel parallel-syncs 25 | 26 | ```text 27 | sentinel parallel-syncs 28 | 29 | 当发生故障转移的时候,原来的从节点就需要向新的master发起复制的操作。这个sentinel parallel-syncs就是说明向新的 30 | master发起复制操作的slave的个数。设置为1表示是从节点轮询发起复制。 31 | ``` 32 | 33 | 34 | # sentinel failover-timeout 35 | 36 | ```text 37 | sentinel failover-timeout 38 | 39 | 故障转移的超时时间,具体作用如下 40 | 41 | 1.如果Sentinel对一个主节点的故障转移失败,那么下次再次对该主节点做故障转移的起始时间是failover-timeout的2倍。 42 | 43 | 2.在从节点选出新的主节点的时候,如果选出来的从节点执行slave no one一直失败,如果超过failover-time时间 44 | 转移故障失败。 45 | 46 | 3. 如果选出新的master成功,还会执行info命令确认是否晋升成功,如果超过failover-time时间,则故障转移失败。 47 | 48 | 4. 如果复制新的master超时,故障转移失败。 49 | 50 | ``` 51 | 52 | # sentinel auth-pass 53 | 54 | ```text 55 | sentinel auth-pass 56 | 57 | Sentinel添加主节点的密码 58 | ``` 59 | 60 | 61 | # sentinel notification-script 62 | 63 | ```text 64 | sentinel notification-script 65 | 66 | Sentinel在故障转移期间,当发何时能一些警告的时候,会触发一些脚本,这里指定了脚本的路径。 67 | ``` 68 | 69 | 70 | 71 | -------------------------------------------------------------------------------- /redis/哨兵/sentinel命令.md: -------------------------------------------------------------------------------- 1 | 2 | # 概述 3 | 4 | >    这里讲述一下Sentinel的命令。 5 | 6 | # sentinel masters 7 | 8 | >    作用:展示所有被监控的主节点以及相关的统计信息 9 | 10 | ![在这里插入图片描述](https://github.com/wuxiaobo000111/pictures/blob/master/2019-04-14/14.jpg?raw=true) 11 | 12 | 13 | # sentinel master 14 | ``` 15 | sentinel master 16 | 17 | 展示指定的master-name的主节点状态以及相关的统计信息。 18 | 其中master-name就是在配置哨兵的时候使用的别名。 19 | 20 | ``` 21 | 22 | # sentinel slaves 23 | 24 | ``` 25 | sentinel slaves 26 | 展示指定master-name的从节点状态以及相关的统计信息。 27 | ``` 28 | 29 | # sentinel sentinels 30 | 31 | ``` 32 | sentinel sentinels 33 | 展示指定master-name节点的sentinel节点的集合。 34 | ``` 35 | 36 | # sentinel get-master-addr-by-name 37 | ``` 38 | sentinel get-master-addr-by-name 39 | 获取主节点的IP地址和端口 40 | 41 | ``` 42 | 43 | # sentinel reset 44 | ``` 45 | sentinel reset 46 | 对复合主节点的配置进行重置,包含清楚主节点的相关状态,重新发送从节点和Sentinel节点。 47 | 48 | ``` 49 | 50 | # sentinel failover 51 | 52 | ``` 53 | sentinel failover 54 | 对指定master-name主节点进行强制故障转移,当故障转移完成之后,其他sentinel节点按照故障转移的结果更新自身配置。 55 | 56 | ``` 57 | 58 | 59 | # sentinel ckquorum 60 | ``` 61 | sentinel ckquorum 62 | 检测当前可达的sentinel节点总数是否达到了的个数。 63 | ``` 64 | 65 | 66 | # sentinel flushconfig 67 | 68 | >    将sentinel节点的配置强制刷新到磁盘上去。 69 | 70 | 71 | 72 | # sentinel remove 73 | ``` 74 | sentinel remove 75 | 76 | 取消当前sentinel节点对于指定主节点的监控 77 | ``` 78 | 79 | # sentinel monitor 80 | 81 | ``` 82 | sentinel monitor 83 | 84 | 通过命令的形式完成sentinel对主节点的监控 85 | ``` 86 | 87 | # sentinel set 88 | 89 | ``` 90 | sentinel set 91 | 动态修改sentinel节点的配置信息 92 | ``` 93 | 94 | 95 | # sentinel is-master-down-by-addr 96 | 97 | >    sentinel节点之间用来交换对主节点是否下线的判断。 98 | 99 | 100 | -------------------------------------------------------------------------------- /redis/哨兵/哨兵原理.md: -------------------------------------------------------------------------------- 1 | 2 | # 概述 3 | 4 | 5 | >    本节将介绍Redis Sentinel的基本实现原理,具体包含以下几个方面:Redis Sentinel的三个定时任务、主观下线和客观下线、Sentinel领导者选举、故障转移。 6 | 7 | 8 | # 三个定时任务 9 | 10 | >    通过定时任务完成对各个节点的发现和监控 11 | 12 | 13 | ## 第一个任务 14 | 15 | >    每隔10秒,每个Sentinel节点会向主节点和从节点发送info命令获取最新的拓扑结构。 16 | 17 | ![在这里插入图片描述](https://github.com/wuxiaobo000111/pictures/blob/master/2019-04-14/15.jpg?raw=true) 18 | 19 | >    这个定时任务的具体作用可以表现为以下三个方面。 20 | 21 | 22 | ```text 23 | 1. 通过向主节点执行info命令,获取从节点的信息,这也是为什么sentinel节点不需要显式配置监控从节点。 24 | 25 | 2. 当有新的从节点加入的时候可以立即感知出来。 26 | 27 | 3. 节点不可达或者故障转移的时候,可以通过info命令实时更新节点拓扑信息。 28 | 29 | ``` 30 | 31 | 32 | ## 第二个任务 33 | 34 | 35 | >    每隔2秒,每个Sentinel节点会向Redis数据节点的__sentinel__:hello频道上发送该Sentinel节点对于主节点的判断以及当前Sentinel节点的信息,同时每个Sentinel节点也会订阅该频道,来了解其他Sentinel节点以及它们对主节点的判断,所以这个定时任务可以完成以下两个工作: 36 | 37 | ```text 38 | 1.是对于sentinel来说的,通过订阅主节点可以了解其他sentinel节点的信息,如果是新加入的,可以将新的sentinel节点保存起来。 39 | 40 | 2.sentinel节点之间交换主节点的状态,作为后面分析客观下线和领导者选举的依据。 41 | 42 | ``` 43 | 44 | 45 | ![在这里插入图片描述](https://github.com/wuxiaobo000111/pictures/blob/master/2019-04-14/17.jpg?raw=true) 46 | 47 | 48 | ## 第三个定时任务 49 | 50 | 51 | >    每隔1秒,每个Sentinel节点会向主节点、从节点、其余Sentinel节点发送一条ping命令做一次心跳检测,来确认这些节点当前是否可达。 52 | 53 | ![在这里插入图片描述](https://github.com/wuxiaobo000111/pictures/blob/master/2019-04-14/16.jpg?raw=true) 54 | 55 | 56 | # 客观下线和主观下线 57 | 58 | 59 | ## 主观下线 60 | 61 | >    每个Sentinel节点会每隔1秒对主节点、从节点、其他Sentinel节点发送ping命令做心跳检测,当这些节点超过down-after-milliseconds没有进行有效回复,Sentinel节点就会对该节点做失败判定,这个行为叫做主观下线。从字面意思也可以很容易看出主观下线是当前Sentinel节点的一家之言,存在误判的可。 62 | 63 | ![在这里插入图片描述](https://github.com/wuxiaobo000111/pictures/blob/master/2019-04-14/18.jpg?raw=true) 64 | 65 | 66 | ## 客观下线 67 | 68 | >   当Sentinel主观下线的节点是主节点时,该Sentinel节点会通过sentinel ismaster-down-by-addr命令向其他Sentinel节点询问对主节点的判断,当超过quorum个数,Sentinel节点认为主节点确实有问题,这时该Sentinel节点会做出客观下线的决定,这样客观下线的含义是比较明显了,也就是大部分Sentinel节点都对主节点的下线做了同意的判定,那么这个判定就是客观的。 69 | 70 | 71 | ![在这里插入图片描述](https://github.com/wuxiaobo000111/pictures/blob/master/2019-04-14/19.jpg?raw=true) 72 | 73 | 74 | # 领导者Sentinel节点的选举 75 | 76 | 77 | >    假如Sentinel节点对于主节点已经做了客观下线,那么是不是就可以立即进行故障转移了?当然不是,实际上故障转移的工作只需要一个Sentinel节点来完成即可,所以Sentinel节点之间会做一个领导者选举的工作,选出一个Sentinel节点作为领导者进行故障转移的工作。Redis使用了Raft算法实现领导者选举。有时间会独立一片文章介绍一下Raft和ZAB算法。 78 | 79 | # 故障转移 80 | 81 | ```text 82 | 1)在从节点列表中选出一个节点作为新的主节点,选择方法如下: 83 | a)过滤:“不健康”(主观下线、断线)、5秒内没有回复过Sentinel节点ping响应、与主节点失联超过down-after-milliseconds*10秒。 84 | b)选择slave-priority(从节点优先级)最高的从节点列表,如果存在则返回,不存在则继续。 85 | c)选择复制偏移量最大的从节点(复制的最完整),如果存在则返回,不存在则继续。 86 | d)选择runid最小的从节点。 87 | 2)Sentinel领导者节点会对第一步选出来的从节点执行slaveof no one命令让其成为主节点。 88 | 3)Sentinel领导者节点会向剩余的从节点发送命令,让它们成为新主节点的从节点,复制规则和parallel-syncs参数有关。 89 | 4)Sentinel节点集合会将原来的主节点更新为从节点,并保持着对其关注,当其恢复后命令它去复制新的主节点。 90 | ``` 91 | 92 | 93 | ![在这里插入图片描述](https://github.com/wuxiaobo000111/pictures/blob/master/2019-04-14/20.jpg?raw=true) -------------------------------------------------------------------------------- /redis/哨兵/如何配置redis哨兵.md: -------------------------------------------------------------------------------- 1 | ![在这里插入图片描述](https://github.com/wuxiaobo000111/pictures/blob/master/2019-04-14/1.jpg?raw=true) 2 | 3 | # 启动主节点 4 | 5 | ![在这里插入图片描述](https://github.com/wuxiaobo000111/pictures/blob/master/2019-04-14/2.jpg?raw=true) 6 | 7 | 8 | # 启动两个从节点 9 | 10 | >    需要注意的是在启动两个从节点之前需要先修改端口 11 | 12 | ![在这里插入图片描述](https://github.com/wuxiaobo000111/pictures/blob/master/2019-04-14/3.jpg?raw=true) 13 | 14 | ![在这里插入图片描述](https://github.com/wuxiaobo000111/pictures/blob/master/2019-04-14/4.jpg?raw=true) 15 | 16 | ![在这里插入图片描述](https://github.com/wuxiaobo000111/pictures/blob/master/2019-04-14/5.jpg?raw=true) 17 | 18 | ![在这里插入图片描述](https://github.com/wuxiaobo000111/pictures/blob/master/2019-04-14/6.jpg?raw=true) 19 | 20 | ![在这里插入图片描述](https://github.com/wuxiaobo000111/pictures/blob/master/2019-04-14/7.jpg?raw=true) 21 | 22 | 23 | >    这个时候主从其实已经搭建好了,其中6379是master节点。6380、5381是slave节点。 24 | 25 | 26 | # Sentinel的启动 27 | 28 | ## 启动sentinel1 29 | 30 | >    修改的配置参数如下所示: 31 | ``` properties 32 | port 26379 33 | logfile "26379.log" 34 | daemonize yes 35 | sentinel monitor mymaster 127.0.0.1 6379 2 36 | sentinel down-after-milliseconds mymaster 30000 37 | sentinel parallel-syncs mymaster 1 38 | sentinel failover-timeout mymaster 180000 39 | 40 | # sentinel monitor mymaster 127.0.0.1 6379 2表示sentinel监控127.0.0.1:6379这个节点。2表示主节点失败了至少 41 | 需要2个Sentinel节点同意。 42 | ``` 43 | 44 | ![在这里插入图片描述](https://github.com/wuxiaobo000111/pictures/blob/master/2019-04-14/8.jpg?raw=true) 45 | 46 | >    验证是否成功。同时启动其他两个节点。只是需要修改上面的端口和日志目录不同就行。 47 | 48 | 49 | 50 | ![在这里插入图片描述](https://github.com/wuxiaobo000111/pictures/blob/master/2019-04-14/10.jpg?raw=true) 51 | 52 | ![在这里插入图片描述](https://github.com/wuxiaobo000111/pictures/blob/master/2019-04-14/11.jpg?raw=true) 53 | 54 | 55 | >    判断是否成功的标志: 56 | ![在这里插入图片描述](https://github.com/wuxiaobo000111/pictures/blob/master/2019-04-14/13.jpg?raw=true) 57 | -------------------------------------------------------------------------------- /redis/慢查询分析.md: -------------------------------------------------------------------------------- 1 | # 慢查询概述 2 | 3 | ![在这里插入图片描述](https://github.com/wuxiaobo000111/pictures/blob/master/2019-03-26/14.jpg?raw=true) 4 | 5 | # 慢查询的两个参数 6 | 7 | ![在这里插入图片描述](https://github.com/wuxiaobo000111/pictures/blob/master/2019-03-26/15.jpg?raw=true) 8 | 9 | >    slowlog-log-slower-than和slowlog-max-len两个参数设置慢查询的标志。前者是少于微秒数就会被记录在慢查询中。当设置为0时候,会记录所有的命令;当设置为小于0时候,则不会记录所有的命令。slowlog-max-len是redis使用一个列表来保存慢查询日志,这个长度就是列表的最大长度。当列表大于最大长度的时候,最早插入的一个命令将会从列表中移除。 10 | 11 | 12 | >    首先配置slowlog-log-slower-than=0,然后重新启动redis。使用java插入100万的数据.有点过分,插入10万就好了。 13 | ```java 14 | Jedis jedis = new Jedis("192.168.88.128",6379); 15 | 16 | for(int i=0; i<100000; i++) { 17 | jedis.set("name"+i,"wuxiaobo"+i); 18 | } 19 | ``` 20 | 21 | >    然后调用keys *命令。通过slowlog get [n]来获取慢查询日志。 22 | 23 | ![在这里插入图片描述](https://github.com/wuxiaobo000111/pictures/blob/master/2019-03-26/16.jpg?raw=true) 24 | 25 | 26 | ![在这里插入图片描述](https://github.com/wuxiaobo000111/pictures/blob/master/2019-03-26/17.jpg?raw=true) 27 | 28 | 29 | ```redis 30 | slowlog len:获取慢查询日志列表当前的长度。 31 | slowlog reset :重置慢查询日志,对列表做清理表操作。 32 | ``` -------------------------------------------------------------------------------- /redis/缓存设计/缓存设计.md: -------------------------------------------------------------------------------- 1 | # 穿透 2 | 3 | >    查询一个根本不存在的数据,缓存层和存储层都不会命中。造成不存在的数据每次请求都要到存储层去查询。失去了缓存保护后端的意义。 4 | 5 | 6 | ## 解决方案 7 | 8 | ```text 9 | 1. 缓存空对象。 10 | 11 | 2.使用布隆过滤器拦截。 12 | 13 | ``` 14 | 15 | # 无底洞现象 16 | 17 | >    当添加更多的分布式节点的时候,性能不但没有好转反而出现了下降,这种现象就被称为缓存的"无底洞"现象。 18 | 19 | # 雪崩 20 | 21 | >    如果缓存由于某些原因不能提供服务,于是所有的请求都去请求存储层,存储层的调用量会暴增。造成存储层宕机的情况。 22 | 23 | 24 | ## 解决方式 25 | 26 | ```text 27 | 28 | 1. 保证缓存层服务的高可用。 29 | 30 | 2. 依赖隔离组件为后端服务限流并且降价。 31 | 32 | 33 | ``` 34 | -------------------------------------------------------------------------------- /redis/阻塞/发现阻塞.md: -------------------------------------------------------------------------------- 1 | # 概述 2 | 3 | >当Redis发生阻塞的时候,最先会是线上服务大量抛出JedisConnectionException.这个只是针对于java程序来说。最常见的做法是在Redis调用的时候有日志统计。推荐使用grafana+prometheus。最常用的使用的总数,慢查询和错误查询。当超过一定阈值的时候发送邮件或者是群。通过这样的方式可以很快通知相关人员进行处理。其实当发生阻塞的时候可以分成为内在原因和外在原因。 4 | 5 | # 内在原因 6 | ``` 7 | 1.API或者是数据结构使用不合理。 8 | 2.CPU饱和问题。 9 | 3.持久化相关阻塞。 10 | ``` 11 | 12 | # 外在原因 13 | 14 | ``` 15 | 1. CPU竞争。 16 | 2. 内存交换。 17 | 3. 网络问题。 18 | ``` 19 | 20 | 21 | >    这里的问题没有展开去讲。我觉得这个地方比较偏重于运维。所以对于一个java程序员只是了解一下。或许这就是菜鸡吧。 22 | -------------------------------------------------------------------------------- /redis/集群/redis cluster.md: -------------------------------------------------------------------------------- 1 | # 集群功能的限制 2 | 3 | ```text 4 | 1. key批量操作支持有限制,如mset,mget,目前只支持具有相同slot值的key执行批量操作,对于映射为不同slot的key由 5 | 于执行mget,mset命令等操作可能存在于多个节点所以不支持。 6 | 7 | 8 | 2. key事务操作支持有限,只支持key在同一个节点上的事务操作,当多个key分布在不同的节点上的时候就无法使用事务功能。 9 | 10 | 3.key做为数据分区的最小粒度,因此不能将一个大的键值对象hash,list映射到不同的节点。 11 | 12 | 4.不支持多数据库空间,只能使用一个数据库db0. 13 | 14 | 5.复制节点只能从主节点复制到从节点。 15 | 16 | ``` 17 | 18 | 19 | # 集群搭建 20 | 21 | ## 准备配置文件 22 | 23 | >    这里使用一台虚拟机,在这台虚拟机中开六个redis节点,模拟redis的集群实现,六台虚拟机做到三主三从。具体节点如下所示。 24 | 25 | 26 | 27 | ![在这里插入图片描述](https://github.com/wuxiaobo000111/pictures/blob/master/2019-04-17/13.jpg?raw=true) 28 | 29 | 30 | >    新建如下目录,以端口号建立目录,一个redis节点对应一个目录. 31 | 32 | 33 | ![在这里插入图片描述](https://github.com/wuxiaobo000111/pictures/blob/master/2019-04-17/14.jpg?raw=true) 34 | 35 | 36 | >    编辑redis.conf目录,对应的文件如下所示,不过是每一份的端口号修改一下即可。 37 | 38 | ![在这里插入图片描述](https://github.com/wuxiaobo000111/pictures/blob/master/2019-04-17/15.png?raw=true) 39 | ``` 40 | bind 192.168.127.130 //绑定服务器IP地址 41 | 42 | port 7000 //绑定端口号,必须修改,以此来区分Redis实例 43 | 44 | daemonize yes //后台运行 45 | 46 | pidfile /var/run/redis-7000.pid //修改pid进程文件名,以端口号命名 47 | 48 | logfile /root/application/program/redis-cluster/7000/redis.log //修改日志文件名称,以端口号为目录来区分 49 | 50 | dir /root/application/program/redis-cluster/7000/ //修改数据文件存放地址,以端口号为目录名来区分 51 | 52 | cluster-enabled yes //启用集群 53 | 54 | cluster-config-file nodes-7000.conf //配置每个节点的配置文件,同样以端口号为名称 55 | 56 | cluster-node-timeout 15000 //配置集群节点的超时时间,可改可不改 57 | 58 | appendonly yes //启动AOF增量持久化策略 59 | 60 | appendfsync always //发生改变就记录日志 61 | ``` 62 | 63 | 64 | >    然后分别启动着六个节点: 65 | 66 | 67 | ![在这里插入图片描述](https://github.com/wuxiaobo000111/pictures/blob/master/2019-04-17/16.jpg?raw=true) 68 | ![在这里插入图片描述](https://github.com/wuxiaobo000111/pictures/blob/master/2019-04-17/17.jpg?raw=true) 69 | 70 | 71 | >这里说一下,这里之后的redis操作可以使用redis-trib.rb直接命令启动,也可以使用命令行启动。在使用redis-trib.rb的过程中出现了很多的问题,这里只是介绍一下使用命令行启动。对于redis-trib.rb可以之后的时间再进行补充。 72 | 73 | 74 | ## 节点握手 75 | 76 | >    到目前为止,已经启动了六个节点了,但是这六个节点还没有形成一个集群,下一步需要做的是节点握手。由客户端命令发起:cluster meet [ip][port]。因为我这里是配置过了,所以才会出现如下的结果。但是这个时候的集群还是处于下线的状态。当集群完成了节点握手,但是还没有分配哈希槽。 77 | 78 | ![在这里插入图片描述](https://github.com/wuxiaobo000111/pictures/blob/master/2019-04-17/18.jpg?raw=true) 79 | 80 | ## 分配哈希槽 81 | 82 | >    自己写了一个垃圾脚本用来分配哈希槽。哈哈哈哈。 83 | ```sh 84 | node1: 85 | #!/bin/bash 86 | n=0 87 | for ((i=n;i<=5461;i++)) 88 | do 89 | /usr/local/redis/bin/redis-cli -h 127.0.0.1 -p 7000 -a dxy CLUSTER ADDSLOTS $i 90 | done 91 | 92 | node2: 93 | #!/bin/bash 94 | n=5462 95 | for ((i=n;i<=10922;i++)) 96 | do 97 | /usr/local/redis/bin/redis-cli -h 127.0.0.1 -p 7001 -a dxy CLUSTER ADDSLOTS $i 98 | done 99 | 100 | node3: 101 | #!/bin/bash 102 | n=10923 103 | for ((i=n;i<=16383;i++)) 104 | do 105 | /usr/local/redis/bin/redis-cli -h 127.0.0.1 -p 7002 -a dxy CLUSTER ADDSLOTS $i 106 | done 107 | 108 | ``` 109 | 110 | 111 | >    然后去操作redis就行了。 112 | 113 | ![在这里插入图片描述](https://github.com/wuxiaobo000111/pictures/blob/master/2019-04-17/19.jpg?raw=true) 114 | 115 | ## 主从配置 116 | 117 | >    上述操作只是使用了其中的三个节点,然后去分配哈希槽。如果实现主从,就需要使用命令实现主从。 118 | ![在这里插入图片描述](https://github.com/wuxiaobo000111/pictures/blob/master/2019-04-17/21.jpg?raw=true) 119 | 120 | 121 | # 总结 122 | 123 | >    我们可以看到如果自己配置实现redis的集群的的确确是超级复杂,当节点挂掉的时候或者是添加节点的时候可能需要自己编写脚本程序了。还是推荐使用redis-trib.rb工具。 124 | 125 | 126 | 127 | 128 | -------------------------------------------------------------------------------- /spring-cloud/Feign/Feign传递复杂对象.md: -------------------------------------------------------------------------------- 1 | # 概述 2 | 3 | >    在Spring MVC中可以直接绑定一个POJO,但是Feign是如何实现绑定复杂对象的。下面提供几种解决方案 4 | 5 | ```text 6 | 7 | 1. 把一个复杂对象拆解成为多个单独的简单对象。 8 | 2. 传递一个Map集合。 9 | 3. 使用GET传递@RequestBody。 10 | 4. 通过Feign拦截器来实现 11 | 12 | 注意:在Spring cloud实践的过程中,使用Cloud一般是作为接口被提供,而且格式多为restful风格,所以在我公司内部使用的是第一种 13 | 解决方式,对于2,3方式基本上不采用。上述4的代码遇到了一点问题,只会会进行补充。 14 | ``` -------------------------------------------------------------------------------- /spring-cloud/Feign/Feign的介绍.md: -------------------------------------------------------------------------------- 1 | 2 | # 什么是Feign 3 | 4 | >    Feign是声明式的Web Service接口。Spring Cloud Feign对Feign进行了增强,可以像使用Spring Web一样使用Feign。同时在Spring Cloud中使用Feign,可以做到使用HTTP访问远程服务。同时整合了Ribbon和HyStrix。 5 | 6 | -------------------------------------------------------------------------------- /spring-cloud/Hystrix/Hystrix异常处理机制.md: -------------------------------------------------------------------------------- 1 | # Hystrix异常处理 2 | 3 | >    Hystrix的异常中有5种情况会被fallback捕获。 4 | 5 | ```text 6 | 1.FAILURE: 执行失败,抛出异常。 7 | 2.TIMEOUT: 执行超时。 8 | 3.SHORT_CIRCUITED: 断路器打开。 9 | 4.THREAD_POOL_REJECTED: 线程池拒绝。 10 | 5.SEMAPHORE_REJECTED: 信号量拒绝。 11 | 需要注意的是BAD_REQUEST并不出触发fallback。 12 | ``` 13 | 14 | 15 | >    如果要想再降级方法中获取异常,只是需要在降级方法参数中加入一个Throwable参数即可。 16 | 17 | ```java 18 | @GetMapping("/getFallbackMethodTest") 19 | @HystrixCommand 20 | public String getFallbackMethodTest(String id){ 21 | throw new RuntimeException("getFallbackMethodTest failed"); 22 | } 23 | 24 | public String fallback(String id, Throwable throwable) { 25 | logger.error(throwable.getMessage()); 26 | return "this is fallback message"; 27 | } 28 | 29 | ``` -------------------------------------------------------------------------------- /spring-cloud/Hystrix/Hystrix概述.md: -------------------------------------------------------------------------------- 1 | # Hystrix概述 2 | 3 | >    Hystrix是开源的针对分布式系统容错处理的组件。 4 | -------------------------------------------------------------------------------- /spring-cloud/Hystrix/Hystrix配置文件说明.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wuxiaobo000111/Java--apollo/53e9cd051a69da77f23bfe7927cf87085c356c18/spring-cloud/Hystrix/Hystrix配置文件说明.md -------------------------------------------------------------------------------- /spring-cloud/Hystrix/Turbine聚合Hystrix.md: -------------------------------------------------------------------------------- 1 | 2 | # 概述 3 | 4 | >    Turbine就是聚合所有相关的Hystrix.stream流的方案,然后在Hystrix DashBoard中显示。在这个项目我们需要借助于原来的三个项目。具体项目地址参考[hystrix-eureka-demo](https://github.com/wuxiaobo000111/java-framework/tree/master/spring-cloud-group/hystrix-group/hystrix-eureka-demo "hystrix-eureka-demo")、[feign-eureka-hystrix-producer](https://github.com/wuxiaobo000111/java-framework/tree/master/spring-cloud-group/feign-group/feign-eureka-hystrix-producer "feign-eureka-hystrix-producer")、 5 | [feign-eureka-hystrix-consumer](https://github.com/wuxiaobo000111/java-framework/tree/master/spring-cloud-group/feign-group/feign-eureka-hystrix-consumer "feign-eureka-hystrix-consumer")、[hystrix-eureka-turbine ](https://github.com/wuxiaobo000111/java-framework/tree/master/spring-cloud-group/hystrix-group/hystrix-eureka-turbine "hystrix-eureka-turbine")。下面讲述一下具体的代码实现。 6 | 7 | 8 | # 项目启动 9 | 10 | >    首先启动hystrix-eureka-demo、feign-eureka-hystrix-producer、 11 | feign-eureka-hystrix-consumer这三个项目。 12 | 13 | # hystrix-eureka-turbine 14 | 15 | ## 依赖 16 | ```xml 17 | 18 | 19 | org.springframework.boot 20 | spring-boot-starter-web 21 | 22 | 23 | org.springframework.cloud 24 | spring-cloud-starter-netflix-eureka-client 25 | 26 | 27 | org.springframework.cloud 28 | spring-cloud-starter-netflix-hystrix 29 | 30 | 31 | org.springframework.cloud 32 | spring-cloud-starter-netflix-hystrix-dashboard 33 | 34 | 35 | org.springframework.cloud 36 | spring-cloud-starter-netflix-turbine 37 | 38 | 39 | 40 | org.springframework.boot 41 | spring-boot-starter-test 42 | test 43 | 44 | 45 | ``` 46 | 47 | ## 启动类 48 | 49 | ```java 50 | package com.bobo.springcloud.learn.hystrixeurekaturbine; 51 | 52 | import org.springframework.boot.SpringApplication; 53 | import org.springframework.boot.autoconfigure.SpringBootApplication; 54 | import org.springframework.cloud.client.discovery.EnableDiscoveryClient; 55 | import org.springframework.cloud.netflix.hystrix.EnableHystrix; 56 | import org.springframework.cloud.netflix.hystrix.dashboard.EnableHystrixDashboard; 57 | import org.springframework.cloud.netflix.turbine.EnableTurbine; 58 | 59 | @SpringBootApplication 60 | @EnableHystrix 61 | @EnableHystrixDashboard 62 | @EnableDiscoveryClient 63 | @EnableTurbine 64 | public class HystrixEurekaTurbineApplication { 65 | 66 | public static void main(String[] args) { 67 | SpringApplication.run(HystrixEurekaTurbineApplication.class, args); 68 | } 69 | 70 | } 71 | 72 | ``` 73 | ## 配置 74 | 75 | ```yml 76 | server: 77 | port: 8094 78 | spring: 79 | application: 80 | name: hystrix-eureka-turbine 81 | eureka: 82 | client: 83 | serviceUrl: 84 | defaultZone: http://192.168.88.128:8761/eureka/,http://192.168.88.128:8760/eureka/ 85 | management: 86 | endpoints: 87 | web: 88 | exposure: 89 | include: "*" 90 | turbine: 91 | app-config: hystrix-eureka-demo,feign-eureka-hystrix-consumer 92 | cluster-name-expression: "'default'" 93 | ``` 94 | 95 | # 结果 96 | 97 | 98 | >    首先启动hystrix-eureka-turbine这个项目。 99 | 100 | ![在这里插入图片描述](https://github.com/wuxiaobo000111/pictures/blob/master/2019-04-27/10.jpg?raw=true) 101 | 102 | >    在地址栏中输入http://localhost:8094/hystrix这个地址,是进入turbine。然后在输入框中输入http://localhost:8094/turbine.stream。进入之后就可以看到两个项目的监控 103 | 104 | 105 | ![在这里插入图片描述](https://github.com/wuxiaobo000111/pictures/blob/master/2019-04-27/11.jpg?raw=true) 106 | 107 | ![在这里插入图片描述](https://github.com/wuxiaobo000111/pictures/blob/master/2019-04-27/12.jpg?raw=true) 108 | 109 | 110 | >    注意,这里是个懒加载,必须在访问接口之后才会出现结果。 111 | -------------------------------------------------------------------------------- /spring-cloud/config/git配置详解.md: -------------------------------------------------------------------------------- 1 | # Git中URI占位符 2 | 3 | Config Server支持占位符的使用,支持{application},{profile},{label},在配置URI的时候,可以通过应用名称来区分对应的仓库然后使用。两个项目的地址分别是[config-client-placeholders-demo](https://github.com/wuxiaobo000111/java-framework/tree/master/spring-cloud-group/config-group/config-client-placeholders-demo "config-client-placeholders-demo")和[config-server-placeholders-demo](https://github.com/wuxiaobo000111/java-framework/tree/master/spring-cloud-group/config-group/config-server-placeholders-demo "config-server-placeholders-demo")。 4 | 5 | # config-server-placeholders-demo 6 | ## pom依赖 7 | 8 | ```xml 9 | 10 | org.springframework.cloud 11 | spring-cloud-config-server 12 | 13 | 14 | org.springframework.boot 15 | spring-boot-starter-actuator 16 | 17 | 18 | org.springframework.boot 19 | spring-boot-starter-test 20 | test 21 | 22 | 23 | ``` 24 | 25 | ## 配置文件 26 | 27 | ```yml 28 | spring: 29 | cloud: 30 | config: 31 | server: 32 | git: 33 | uri: https://github.com/wuxiaobo000111/{application} 34 | username: 35 | password: 36 | search-paths: SC-BOOK-CONFIG 37 | application: 38 | name: config-server-placeholders-demo 39 | server: 40 | port: 8127 41 | logging: 42 | level: 43 | root: debug 44 | 45 | ``` 46 | 47 | 在uri路径中使用的是占位符,和client中的spring.cloud.config.name对应 48 | 49 | ## 启动类 50 | 51 | ```java 52 | package com.bobo.springcloud.learn.configserverdemo; 53 | 54 | import org.springframework.boot.SpringApplication; 55 | import org.springframework.boot.autoconfigure.SpringBootApplication; 56 | import org.springframework.cloud.config.server.EnableConfigServer; 57 | 58 | @SpringBootApplication 59 | @EnableConfigServer 60 | public class ConfigServerDemoApplication { 61 | 62 | public static void main(String[] args) { 63 | SpringApplication.run(ConfigServerDemoApplication.class, args); 64 | } 65 | 66 | } 67 | 68 | 69 | ``` 70 | 71 | 72 | 73 | # config-server-placeholders-demo 74 | 75 | 和[config-client-demo](https://github.com/wuxiaobo000111/java-framework/tree/master/spring-cloud-group/config-group/config-client-demo "config-client-demo")项目基本上一致,需要改动的就是bootstrap.yml中的name属性,修改为spring-cloud-config。 76 | 77 | 配置中心的结构如下所示: 78 | 79 | ![在这里插入图片描述](https://github.com/wuxiaobo000111/pictures/blob/master/2019-05-10/84.jpg?raw=true) 80 | -------------------------------------------------------------------------------- /spring-cloud/config/使用多个git库.md: -------------------------------------------------------------------------------- 1 | 代码有点问题,暂时没有做 2 | todo -------------------------------------------------------------------------------- /spring-cloud/consul/consul概念介绍.md: -------------------------------------------------------------------------------- 1 | >原博客地址:https://blog.csdn.net/love_zngy/article/details/82216696 2 | # 什么是consul 3 | 4 | >    Consul 是 HashiCorp 公司推出的开源工具,用于实现分布式系统的服务发现与配置。与其它分布式服务注册与发现的方案,Consul 的方案更“一站式”,内置了服务注册与发现框 架、分布一致性协议实现、健康检查、Key/Value 存储、多数据中心方案,不再需要依赖其它工具(比如 ZooKeeper 等)。使用起来也较 为简单。Consul 使用 Go 语言编写,因此具有天然可移植性(支持Linux、windows和Mac OS X);安装包仅包含一个可执行文件,方便部署,与 Docker 等轻量级容器可无缝配合。 5 | 6 | # consul能做什么 7 | 8 | ```text 9 | 10 | 1.服务发现。有了consul,服务可以通过DNS或者HTTP直接找到它所依赖的服务。 11 | 2.健康检查。consul提供了健康检查的机制,从简单的服务端是否返回200的响应代码到较为复杂的内存使用率是否低于90%。 12 | 3.K/V存储。应用程序可以根据需要使用consul的Key/Value存储。Consul提供了简单易用的http接口来满足用户的动态配置、 13 | 特征标记、协调、leader选举等需求。 14 | 4.多数据中心。Consul原生支持多数据中心。这意味着用户不用为了多数据中心自己做抽象。 15 | 16 | ``` 17 | 18 | # 和其他服务发现做对比 19 | 20 | ![在这里插入图片描述](https://github.com/wuxiaobo000111/pictures/blob/master/2019-05-10/37.jpg?raw=true) 21 | -------------------------------------------------------------------------------- /spring-cloud/consul/consul的安装.md: -------------------------------------------------------------------------------- 1 | # 下载 2 | 3 | >    下载地址是https://www.consul.io/downloads.html。然后上传到自己的linux服务器。 4 | 5 | 6 | # 启动命令 7 | 8 | >    ./consul agent -dev -ui -client 192.168.88.128 -dev表示是启动的开发环境下的一个Server节点。-ui表示开启consul的UI页面。 -clent 192.168.88.128 表示是可以通过这个地址访问consul。注意:这里其实有一个很大的坑,对于初学者来说可能是一个问题就是说如果指定agent -dev 也是只允许当前ip注册,但这个是本地开发时用的,正式服务器往往不用。所以如下的配置是错误的: 9 | 10 | ```yml 11 | server: 12 | port: 8109 13 | spring: 14 | application: 15 | name: consul-server 16 | cloud: 17 | consul: 18 | discovery: 19 | service-name: consul-server 20 | ip-address: 192.168.88.128 21 | # consul所在的ip地址 22 | host: 192.168.88.128 23 | # 访问端口 24 | port: 8500 25 | 26 | ``` 27 | 28 | ![在这里插入图片描述](https://github.com/wuxiaobo000111/pictures/blob/master/2019-05-10/38.jpg?raw=true) 29 | 30 | 31 | 32 | 33 | # consule常用的命令 34 | 35 | 36 | ![在这里插入图片描述](https://github.com/wuxiaobo000111/pictures/blob/master/2019-05-10/39.jpg?raw=true) 37 | 38 | # consul agent 命令的常用选项 39 | 40 | ```text 41 | 42 | -data-dir 作用:指定agent储存状态的数据目录 43 | 这是所有agent都必须的.对于server尤其重要,因为他们必须持久化集群的状态 44 | 45 | -config-dir 作用:指定service的配置文件和检查定义所在的位置 46 | 通常会指定为”某一个路径/consul.d”(通常情况下,.d表示一系列配置文件存放的目录) 47 | 48 | -config-file 作用:指定一个要装载的配置文件 49 | 该选项可以配置多次,进而配置多个配置文件(后边的会合并前边的,相同的值覆盖) 50 | 51 | -dev 作用:创建一个开发环境下的server节点 52 | 该参数配置下,不会有任何持久化操作,即不会有任何数据写入到磁盘.这种模式不能用于生产环境(因为第二条) 53 | 54 | -bootstrap-expect 作用:该命令通知consul server我们现在准备加入的server节点个数,该参数是为了延迟日志复制的启动 55 | 直到我们指定数量的server节点成功的加入后启动。 56 | 57 | -node 作用:指定节点在集群中的名称 58 | 该名称在集群中必须是唯一的(默认采用机器的host) 59 | 推荐:直接采用机器的IP 60 | 61 | -bind 作用:指明节点的IP地址 62 | 有时候不指定绑定IP,会报Failed to get advertise address: Multiple private IPs found. 63 | Please configure one. 的异常 64 | 65 | -server 作用:指定节点为server 66 | 每个数据中心(DC)的server数推荐至少为1,至多为5所有的server都采用raft一致性算法来确保事务的一致性 67 | 和线性化,事务修改了集群的状态,且集群的状态保存在每一台server上保证可用性server也是与其他DC交互的门面 68 | (gateway) 69 | 70 | 71 | -client 作用:指定节点为client,指定客户端接口的绑定地址,包括:HTTP、DNS、RPC 默认是127.0.0.1,只允许回环 72 | 接口访问若不指定为-server,其实就是-client 73 | 74 | -join 作用:将节点加入到集群 75 | 76 | -datacenter(老版本叫-dc,-dc已经失效) 77 | 78 | ``` 79 | 80 | -------------------------------------------------------------------------------- /spring-cloud/consul/consul的配置中心.md: -------------------------------------------------------------------------------- 1 | # 概述 2 | 3 | >    这里主要给出关于基于consul作为配置中心的示例代码,这里给出了项目地址分别是[consul-demo-config](https://github.com/wuxiaobo000111/java-framework/tree/master/spring-cloud-group/consul-group/consul-demo-config "consul-demo-config") 4 | 5 | # 代码 6 | 7 | ## pom依赖 8 | 9 | ```xml 10 | 11 | 12 | 13 | org.springframework.boot 14 | spring-boot-starter-web 15 | 16 | 17 | org.springframework.cloud 18 | spring-cloud-starter-consul-config 19 | 20 | 21 | 22 | org.springframework.boot 23 | spring-boot-starter-test 24 | test 25 | 26 | 27 | ``` 28 | 29 | ## 配置文件 30 | 31 | ```yml 32 | spring: 33 | application: 34 | name: consul-demo-config 35 | cloud: 36 | consul: 37 | host: 127.0.0.1 38 | port: 8500 39 | server: 40 | port: 8112 41 | ``` 42 | 43 | ## 启动类 44 | 45 | ```java 46 | package com.bobo.springcloud.learn.consuldemoconfig; 47 | 48 | import org.springframework.boot.SpringApplication; 49 | import org.springframework.boot.autoconfigure.SpringBootApplication; 50 | import org.springframework.cloud.client.discovery.EnableDiscoveryClient; 51 | import org.springframework.cloud.context.config.annotation.RefreshScope; 52 | 53 | @SpringBootApplication 54 | @EnableDiscoveryClient 55 | @RefreshScope 56 | public class ConsulDemoConfigApplication { 57 | 58 | public static void main(String[] args) { 59 | SpringApplication.run(ConsulDemoConfigApplication.class, args); 60 | } 61 | 62 | } 63 | 64 | 65 | ``` 66 | 67 | ## 接口 68 | 69 | ```java 70 | package com.bobo.springcloud.learn.consuldemoconfig; 71 | 72 | import org.springframework.beans.factory.annotation.Value; 73 | import org.springframework.web.bind.annotation.GetMapping; 74 | import org.springframework.web.bind.annotation.RestController; 75 | 76 | @RestController 77 | public class HelloController { 78 | // 读取远程配置 79 | @Value("${name}") 80 | private String name; 81 | 82 | @GetMapping(value = "/getName") 83 | public String getName () { 84 | return name; 85 | } 86 | 87 | } 88 | 89 | ``` -------------------------------------------------------------------------------- /spring-cloud/eureka/eureka rest api.md: -------------------------------------------------------------------------------- 1 | # eureka REST API 接口 2 | 3 | 4 | ![在这里插入图片描述](https://github.com/wuxiaobo000111/pictures/blob/master/2019-04-16/2.jpg?raw=true) 5 | 6 | -------------------------------------------------------------------------------- /spring-cloud/eureka/eureka配置项.md: -------------------------------------------------------------------------------- 1 | # eureka常用配置项 2 | 3 | ``` 4 | eureka.client.allow-redirects:是否允许重定向Eureka客户端请求到其他或者备份服务器,默认为fasle 5 | 6 | eureka.client.eureka-connection-idle-timeout-seconds:HTTP连接到eureka服务器可以在关闭之前保持空闲的时间(几秒钟)。 7 | 8 | eureka.client.eureka-server-connect-timeout-seconds:表示连接Eureka服务器,等待多长时间算超时 9 | 10 | eureka.client.eureka-server-port: Eureka Server端口 11 | 12 | eureka.client.eureka-server-d-n-s-name:获取要查询的DNS名称以获得eureka服务器的列表。 13 | 14 | eureka.client.eureka-server-read-timeout-seconds:示在从eureka服务器读取数据之前需要等待多长时间(以秒为单位) 15 | 16 | eureka.client.eureka-server-total-connections:从eureka客户端到所有eureka服务器的所允许连接总数。 17 | 18 | eureka.client.eureka-server-total-connections-per-host:设置每一个主机所允许的到Eureka Server连接的数量 19 | 20 | eureka.client.fetch-registry: 是否允许客户端向Eureka 注册表获取信息,一般服务器为设置为false,客户端设置为true 21 | 22 | eureka.client.register-with-eureka:是否允许向Eureka Server注册信息,默认true,如果是服务器端,应该设置为false 23 | 24 | eureka.client.fetch-remote-regions-registry:逗号分隔的区域列表,用于获取eureka注册信息 25 | 26 | eureka.client.g-zip-content:从服务器端获取数据是否需要压缩 27 | 28 | eureka.client.prefer-same-zone-eureka: 是否优先使选择相同Zone的实例,默认为true 29 | 30 | eureka.client.registry-fetch-interval-seconds:多长时间从Eureka Server注册表获取一次数据,默认30s 31 | 32 | eureka.client.service-url:可用区域映射,列出完全合格的url与eureka服务器通信。每个值可以是一个URL,也可以是一个逗号分隔的替代位置列表。 33 | 34 | eureka.dashboard.enabled: 是否启用Eureka首页,默认为true 35 | 36 | eureka.dashboard.path: 默认为/ 37 | 38 | eureka.instance.appname:在eureka注册的应用程序的名称。 39 | 40 | eureka.instance.app-group-name:在eureka注册的应用程序的组名称 41 | 42 | eureka.instance.health-check-url: 健康检查绝对路径 43 | 44 | eureka.instance.health-check-url-path:健康检查相对路径 45 | 46 | eureka.instance.hostname:设置主机名 47 | 48 | eureka.instance.instance-id:设置注册实例的id 49 | 50 | eureka.instance.lease-expiration-duration-in-seconds:设置多长时间意味着租约到期,默认90 51 | 52 | eureka.instance.lease-renewal-interval-in-seconds:表示Eureka客户端需要发送心跳到eureka服务器的频率(以秒为单位),以表明它仍然存在。指定的期间内如果没有收到心跳leaseExpirationDurationInSeconds 53 | 54 | eureka.instance.metadata-map:可以设置元数据 55 | 56 | eureka.instance.prefer-ip-address: 实例名以IP,但是建议hostname,默认为false 57 | ``` -------------------------------------------------------------------------------- /spring-cloud/gateway/gateWay内置的Filter.md: -------------------------------------------------------------------------------- 1 | 2 | # 补充 3 | 4 | 关于这个知识点的代码地址如下所示:https://github.com/wuxiaobo000111/java-framework/tree/master/spring-cloud-group/gateway-group/gateway-service 和 5 | https://github.com/wuxiaobo000111/java-framework/tree/master/spring-cloud-group/gateway-group/gateway-inner-filter-demo。 每个地方的代码都只是会指定除和这一部分有关的代码。 6 | 7 | # 概述 8 | 9 |     Spring Cloud Gatewat中内置很多的路由过滤工厂,当然可以自己根据实际应用场景需要定制的自己的路由过滤器工厂。路由过滤器允许以某种方式修改请求进来的http请求或返回的http响应。路由过滤器主要作用于需要处理的特定路由。Spring Cloud Gateway提供了很多种类的过滤器工厂,过滤器的实现类将近二十多个。总得来说,可以分为七类: Header,Parameter.Path、 Status, Redirect跳转、Hytrix熔断和RateLimiter,下面介绍一下Spring Cloud Gateway中的Filter工厂。 10 | 11 | 12 | # AddRequestHeader过滤器工厂 13 | 14 | AddRequestHeader过滤器工厂的作用就是对于匹配的上的请求加上Header。 15 | 16 | ```yml 17 | spring: 18 | application: 19 | name: gateway-inner-filter-demo 20 | cloud: 21 | gateway: 22 | routes: 23 | - id: add_request_parameter_route 24 | uri: http://localhost:8140 25 | filters: 26 | - AddRequestHeader=X-Request-Acme, ValueB 27 | # 不能少 28 | predicates: 29 | - After=2017-01-20T17:42:47.789-07:00[America/Denver] 30 | ``` 31 | 32 | ```java 33 | 34 | /** 35 | * @param request 36 | * @param response 37 | * @return 38 | */ 39 | @GetMapping("/test/head") 40 | public String testGatewayHead(HttpServletRequest request, HttpServletResponse response){ 41 | String head=request.getHeader("X-Request-Acme"); 42 | return "return head info:"+head; 43 | } 44 | 45 | ``` 46 | 47 | 通过访问 http://localhost:8139/test/head 可以拿到想要的结果。 48 | 49 | 50 | # AddRequestParameter过滤器 51 | 52 | 该过滤器作用是对匹配上的请求路由添加请求参数。 53 | 54 | ```yml 55 | 56 | spring: 57 | application: 58 | name: gateway-inner-filter-demo 59 | cloud: 60 | gateway: 61 | routes: 62 | # - id: add_request_head_route 63 | # uri: http://localhost:8140 64 | # filters: 65 | # - AddRequestHeader=X-Request-Acme, ValueB 66 | # # 不能少 67 | # predicates: 68 | # - After=2017-01-20T17:42:47.789-07:00[America/Denver] 69 | - id: add_request_parameter_route 70 | uri: http://localhost:8140 71 | filters: 72 | - AddRequestParameter=example, wuxiaobo 73 | # 不能少 74 | predicates: 75 | - After=2017-01-20T17:42:47.789-07:00[America/Denver] 76 | ``` 77 | 78 | ```java 79 | 80 | @GetMapping("/test/addRequestParameter") 81 | public String addRequestParameter(HttpServletRequest request, HttpServletResponse response){ 82 | String parameter=request.getParameter("example"); 83 | return "return addRequestParameter info:"+parameter; 84 | } 85 | 86 | 87 | ``` 88 | 89 | 然后去请求 http://localhost:8139/test/addRequestParameter 这个地址就行了。 90 | 91 | 92 | 93 | 其他的功能就不在这里一一列举了,有兴趣的小伙伴可以访问 https://cloud.spring.io/spring-cloud-gateway/single/spring-cloud-gateway.html#_preservehostheader_gatewayfilter_factory 官方网站进行查询。 94 | 95 | 96 | -------------------------------------------------------------------------------- /spring-cloud/gateway/gatewayFileter和GlobalFilter.md: -------------------------------------------------------------------------------- 1 | # Gateway Filter和Global Filter 概述 2 | 3 |     Spring Cloud Gateway中的Filter从接口实现上分为两种:一种是Gateway Filter,另外一种是Global Filter。那要怎么理解这两种Filter的设计呢?通过和Gateway的核心设计者进行设计交流,我对这两者有了更深刻的了解。下面我们介绍一下Gateway Filter和Global Filter之间的区别和联系。 4 | ## Gateway Filter概述 5 |      Gateway Filter是从Web Filter中复制过来的,相当于一个Filter过滤器,可以对访问的URL过滤,进行横切处理(切面处理),应用场景包括超时、安全等。 6 | ## Global Filter概述 7 |      Spring Cloud Gateway定义了Global Filter的接口,让我们可以自定义实现自己的Globa)Filtero Global Filter是一个全局的Filter,作用于所有路由。 8 | ## Gateway Filter和Global Filter的区别 9 |      从路由的作用范围来看, Global filter会被应用到所有的路由上,而Gateway filter则应用到单个路由或者一个分组的路由上。从源码设计来看, Gateway Filter和Global Filter两个接口中定义的方法一样都是Mono filter),唯一的区别就是GatewayFilter继承了ShortcutConfigurable,而GlobalFilter没有任何继承。 10 | -------------------------------------------------------------------------------- /spring-cloud/gateway/gateway基于服务发现的路由规则.md: -------------------------------------------------------------------------------- 1 | # 介绍 2 | 3 |     在前面介绍Zuul的章节中,我们知道Spring Cloud对Zuul进行封装处理之后;当通过Zuul访问后端微服务时,基于服务发现的默认路由规则是:http://zul-host:zul port/微服务在Eureka上的serviceld/**, Spring Cloud Gateway在设计的时候考虑从Zuul迁移到Gateway的兼容性和迁移成本等, Gateway基于服务发现的路由规则和Zuul的设计类似,但是也有很大差别。但是Spring Cloud Gateway基于服务发现的路由规则,在不同注册中心下其差异如下: 4 |         如果把Gateway注册到Eureka上,通过网关转发服务调用,访问网关的URL是 5 | http://Gateway_HOST:Gateway-PORT/大写的serviceld/*,其中服务名默认必须是大写,否则会抛404错误,如果服务名要用小写访问,可以在属性配置文件里面加 6 | spring.cloud.gateway.discovery.locator.lowerCaseServiceld-true配置解决。 7 | 8 |         如果把Gateway注册到Zookeeper上,通过网关转发服务调用,服务名默认小写,因此不需要做任何处理。 9 | 10 |         如果把Gateway注册到Consul上,通过网关转发服务调用,服务名默认小写,也不需要做人为修改。 11 | 12 |     下面会给出示例代码。 13 | 14 | # 示例 15 | 16 | 17 | https://github.com/wuxiaobo000111/java-framework/tree/master/spring-cloud-group/gateway-group/gateway-eureka-gateway 18 | 19 | https://github.com/wuxiaobo000111/java-framework/tree/master/spring-cloud-group/gateway-group/gateway-eureka-producer 20 | 21 | https://github.com/wuxiaobo000111/java-framework/tree/master/spring-cloud-group/gateway-group/geteway-eureka-consumer 22 | 23 | 这里给出了项目地址。 24 | 25 | -------------------------------------------------------------------------------- /spring-cloud/ribbon/Ribbon入门教程.md: -------------------------------------------------------------------------------- 1 | # Ribbon入门小例子 2 | 3 | # 服务端 4 | 5 | ## 依赖 6 | ```xml 7 | 8 | 9 | org.springframework.boot 10 | spring-boot-starter-web 11 | 12 | 13 | org.springframework.cloud 14 | spring-cloud-starter-netflix-eureka-client 15 | 16 | 17 | 18 | org.springframework.boot 19 | spring-boot-starter-test 20 | test 21 | 22 | 23 | ``` 24 | 25 | ## 启动类 26 | ```java 27 | package com.bobo.springcloud.learn.ribboneurekademoproducer; 28 | 29 | import org.springframework.boot.SpringApplication; 30 | import org.springframework.boot.autoconfigure.SpringBootApplication; 31 | import org.springframework.cloud.client.discovery.EnableDiscoveryClient; 32 | 33 | @EnableDiscoveryClient 34 | @SpringBootApplication 35 | public class RibbonEurekaDemoProducerApplication { 36 | 37 | public static void main(String[] args) { 38 | SpringApplication.run(RibbonEurekaDemoProducerApplication.class, args); 39 | } 40 | 41 | } 42 | 43 | ``` 44 | 45 | ## 接口 46 | 47 | ```java 48 | package com.bobo.springcloud.learn.ribboneurekademoproducer.controller; 49 | 50 | import org.springframework.web.bind.annotation.GetMapping; 51 | import org.springframework.web.bind.annotation.RestController; 52 | 53 | @RestController 54 | public class HelloController { 55 | 56 | @GetMapping(value = "/hello") 57 | public String hello (String name) { 58 | return "hello," + name; 59 | } 60 | } 61 | 62 | ``` 63 | 64 | ## 配置文件 65 | 66 | ```yml 67 | server: 68 | port: 8100 69 | eureka: 70 | client: 71 | serviceUrl: 72 | defaultZone: http://192.168.88.128:8761/eureka/,http://192.168.88.128:8760/eureka/ 73 | spring: 74 | application: 75 | name: ribbon-eureka-demo-producer 76 | 77 | 78 | ``` 79 | 80 | # 客户端 81 | 82 | ## 依赖 83 | 84 | ```xml 85 | 86 | 87 | org.springframework.boot 88 | spring-boot-starter-web 89 | 90 | 91 | org.springframework.cloud 92 | spring-cloud-starter-netflix-eureka-client 93 | 94 | 95 | org.springframework.cloud 96 | spring-cloud-starter-netflix-ribbon 97 | 98 | 99 | 100 | org.springframework.boot 101 | spring-boot-starter-test 102 | test 103 | 104 | 105 | ``` 106 | 107 | ## 启动类 108 | 109 | ```java 110 | package com.bobo.springcloud.learn.ribboneurekademoconsumer; 111 | 112 | import org.springframework.boot.SpringApplication; 113 | import org.springframework.boot.autoconfigure.SpringBootApplication; 114 | import org.springframework.cloud.client.discovery.EnableDiscoveryClient; 115 | import org.springframework.cloud.client.loadbalancer.LoadBalanced; 116 | import org.springframework.context.annotation.Bean; 117 | import org.springframework.web.client.RestTemplate; 118 | 119 | @EnableDiscoveryClient 120 | @SpringBootApplication 121 | public class RibbonEurekaDemoConsumerApplication { 122 | 123 | @Bean 124 | @LoadBalanced 125 | public RestTemplate restTemplate () { 126 | return new RestTemplate(); 127 | } 128 | public static void main(String[] args) { 129 | SpringApplication.run(RibbonEurekaDemoConsumerApplication.class, args); 130 | } 131 | 132 | } 133 | 134 | ``` 135 | 136 | ## 接口 137 | 138 | ```java 139 | package com.bobo.springcloud.learn.ribboneurekademoconsumer.controller; 140 | 141 | import org.springframework.beans.factory.annotation.Autowired; 142 | import org.springframework.web.bind.annotation.GetMapping; 143 | import org.springframework.web.bind.annotation.RestController; 144 | import org.springframework.web.client.RestTemplate; 145 | 146 | @RestController 147 | public class HelloController { 148 | 149 | @Autowired 150 | private RestTemplate restTemplate; 151 | 152 | @GetMapping(value = "/hello") 153 | public String hello (String name) { 154 | return restTemplate.getForObject("http://RIBBON-EUREKA-DEMO-PRODUCER/hello?name="+name,String.class); 155 | } 156 | } 157 | 158 | ``` 159 | 160 | ## 配置文件 161 | 162 | ```yml 163 | server: 164 | port: 8101 165 | eureka: 166 | client: 167 | serviceUrl: 168 | defaultZone: http://192.168.88.128:8761/eureka/,http://192.168.88.128:8760/eureka/ 169 | spring: 170 | application: 171 | name: ribbon-eureka-demo-consumer 172 | ``` 173 | 174 | ## 启动两个项目 175 | 176 | 177 | > 访问结果如下 178 | 179 | ![在这里插入图片描述](https://github.com/wuxiaobo000111/pictures/blob/master/2019-05-10/9.jpg?raw=true) 180 | 181 | -------------------------------------------------------------------------------- /spring-cloud/ribbon/Ribbon实践.md: -------------------------------------------------------------------------------- 1 | # Ribbon负载均衡策略 2 | 3 | |策略类 |备注 | 描述 | 4 | |---|---|---| 5 | |RandomRule| 随机策略|随机选择server | 6 | |RoundRobinRule| 轮询策略 |按顺序循环选择server| 7 | |RetryRule |重试策略|在一个配置时间段内当选择server不成功,则一直尝试选择一个可用的server| 8 | |BestAvailableRule |最低并发策略|逐个考察server,如果server断路器打开,则忽略,再选择其中并发连接最低的server| 9 | |AvailabilityFilteringRule |可用过法策略| 过滤掉一直连接失败并被标记为circuit tripped的server,过滤掉那些高并发连接的server (activeconnections超过配置的阈值) | 10 | |ResponseTimeWeightedRule|相应时间加权策略|根据server的响应时间分配权重。响应时间越长,权重越低,被响应时间|选择到的概率就越低;响应时间越短,权重越高,被选择到的概率就越低;相应时间越短,权重越高,被选择到的概率就越高。这个策略很贴切,综合了各种因素,如:网络、磁盘、IO等,这些因素直接影响着响应时间 11 | |ZoneAvoidanceRule |区域权衡策略|综合判断server所在区域的性能和server的可用性轮询选择server,并且判定一个AwsZone的运行性能是否可用,剔除不可用的Zone中的所有server| 12 | 13 | # 如何设置负载均衡 14 | 15 | ## 全局策略设置 16 | 17 | ```java 18 | package com.bobo.springcloud.learn.ribboneurekademoconsumer.config; 19 | 20 | import com.netflix.loadbalancer.IRule; 21 | import com.netflix.loadbalancer.RandomRule; 22 | import org.springframework.context.annotation.Bean; 23 | import org.springframework.context.annotation.Configuration; 24 | 25 | /** 26 | * 指定ribbon的负载均衡策略 27 | */ 28 | @Configuration 29 | public class RibbonRuleConfig { 30 | 31 | @Bean 32 | public IRule iRule () { 33 | return new RandomRule(); 34 | } 35 | } 36 | 37 | ``` 38 | 39 | ## 基于注解的策略配置 40 | 41 | ```java 42 | package com.bobo.springcloud.learn.ribboneurekademoconsumer.config; 43 | 44 | public @interface AvoidScan { 45 | 46 | } 47 | 48 | package com.bobo.springcloud.learn.ribboneurekademoconsumer.config; 49 | 50 | import com.netflix.client.config.IClientConfig; 51 | import com.netflix.loadbalancer.IRule; 52 | import com.netflix.loadbalancer.RandomRule; 53 | import org.springframework.beans.factory.annotation.Autowired; 54 | import org.springframework.context.annotation.Bean; 55 | import org.springframework.context.annotation.Configuration; 56 | 57 | /** 58 | * 指定ribbon的负载均衡策略 59 | */ 60 | @Configuration 61 | @AvoidScan 62 | public class RibbonRuleConfig { 63 | 64 | @Autowired 65 | IClientConfig config; 66 | 67 | @Bean 68 | public IRule ribbonRule(IClientConfig config) { 69 | return new RandomRule(); 70 | } 71 | } 72 | 73 | 74 | @RibbonClient(name = "ribbon-eureka-demo-producer", configuration = RibbonRuleConfig.class) 75 | @ComponentScan(excludeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION, value = {AvoidScan.class})}) 76 | 77 | 78 | 79 | ``` 80 | 81 | >     RibbonClient是对name的服务使用configuration中的配置类。 82 | > @ComponentScan是让Spring不去扫描被@AvoidScan注解标记的配置类。 83 | 84 | 85 | # Ribbon的超时和重试 86 | 87 | ```yml 88 | ribbon-eureka-demo-producer: 89 | ribbon: 90 | ConnectTimeout: 30000 91 | ReadTimeout: 30000 92 | # 对第一次请求的服务的重试次数 93 | ManAutoRetries: 1 94 | # 要重试的下一个服务的最大数量。 95 | MaxAutoRetriesNextServer: 1 96 | OktoRetryOnAllOpreations: all 97 | ``` 98 | 99 | # Ribbon的饥饿加载 100 | 101 | >     Ribbon使用的是懒加载。可以通过配置的方式修改为饥饿加载。 102 | 103 | ```yml 104 | 105 | ribbon: 106 | eager-load: 107 | enabled: true 108 | clients: ribbon-eureka-demo-producer 109 | ``` 110 | -------------------------------------------------------------------------------- /spring-cloud/ribbon/使用ribbon调用第三方HTTPS接口.md: -------------------------------------------------------------------------------- 1 | # 概述 2 | 3 | >    这里讲述一下如何通过ribbon中的RestTemplate实现对第三方https接口的请求。话不多说,直接上代码。 4 | 5 | # 实现 6 | 7 | ## 配置文件 8 | 9 | ```yml 10 | ribbon-client-config: 11 | ConnectTimeout: 3000 12 | ReadTimeout: 3000 13 | ``` 14 | 15 | ## 加入配置 16 | 17 | ```java 18 | 19 | package com.didapinche.thrift.cmstaxi.config; 20 | 21 | import org.apache.http.client.HttpClient; 22 | import org.apache.http.client.config.RequestConfig; 23 | import org.apache.http.config.Registry; 24 | import org.apache.http.config.RegistryBuilder; 25 | import org.apache.http.conn.socket.ConnectionSocketFactory; 26 | import org.apache.http.conn.socket.PlainConnectionSocketFactory; 27 | import org.apache.http.conn.ssl.SSLConnectionSocketFactory; 28 | import org.apache.http.impl.client.HttpClientBuilder; 29 | import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; 30 | import org.springframework.beans.factory.annotation.Value; 31 | import org.springframework.context.annotation.Bean; 32 | import org.springframework.context.annotation.Configuration; 33 | import org.springframework.http.client.ClientHttpRequestFactory; 34 | import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; 35 | import org.springframework.web.client.RestTemplate; 36 | 37 | @Configuration 38 | public class RestTemplateConfig { 39 | 40 | @Value("${ribbon-client-config.ConnectTimeout}") 41 | private int connectTimeout; 42 | 43 | @Value("${ribbon-client-config.ReadTimeout}") 44 | private int readTimeout; 45 | 46 | 47 | @Bean 48 | public RestTemplate restTemplate() 49 | { 50 | ClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory(httpClient()); 51 | return new RestTemplate(requestFactory); 52 | } 53 | 54 | private HttpClient httpClient() 55 | { 56 | // 支持HTTP、HTTPS 57 | Registry registry = RegistryBuilder. create() 58 | .register("http", PlainConnectionSocketFactory.getSocketFactory()) 59 | .register("https", SSLConnectionSocketFactory.getSocketFactory()) 60 | .build(); 61 | PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(registry); 62 | connectionManager.setMaxTotal(200); 63 | connectionManager.setDefaultMaxPerRoute(100); 64 | connectionManager.setValidateAfterInactivity(2000); 65 | RequestConfig requestConfig = RequestConfig.custom() 66 | // 服务器返回数据(response)的时间,超时抛出read timeout 67 | .setSocketTimeout(readTimeout) 68 | // 连接上服务器(握手成功)的时间,超时抛出connect timeout 69 | .setConnectTimeout(connectTimeout) 70 | .build(); 71 | return HttpClientBuilder.create().setDefaultRequestConfig(requestConfig).setConnectionManager(connectionManager).build(); 72 | } 73 | } 74 | 75 | ``` 76 | 77 | ## util工具类 78 | 79 | ```java 80 | package com.didapinche.thrift.cmstaxi.util; 81 | 82 | import com.didapinche.server.commons.common.json.JsonMapper; 83 | import org.slf4j.Logger; 84 | import org.slf4j.LoggerFactory; 85 | import org.springframework.http.HttpEntity; 86 | import org.springframework.http.HttpHeaders; 87 | import org.springframework.http.MediaType; 88 | import org.springframework.web.client.RestTemplate; 89 | 90 | public class RestTemplateUtil { 91 | 92 | private static Logger logger = LoggerFactory.getLogger(RestTemplateUtil.class); 93 | 94 | private static RestTemplate restTemplate = SpringUtils.getBean("restTemplate",RestTemplate.class); 95 | 96 | public static T postForObject (String url, Object object, Class tClass) { 97 | HttpHeaders headers = new HttpHeaders(); 98 | headers.setContentType(MediaType.APPLICATION_JSON_UTF8); 99 | HttpEntity requestEntity = new HttpEntity<>(object,headers); 100 | T post = restTemplate.postForObject(url, object, tClass); 101 | logger.info("RestTemplateUtil postForObject url:{},params:{},result:{}",url,JsonMapper.toJson(object), 102 | JsonMapper.toJson(post)); 103 | return post; 104 | } 105 | } 106 | 107 | ``` 108 | 109 | ## 测试类 110 | 111 | ```java 112 | 113 | @Test 114 | public void testHttps() 115 | { 116 | String url = "https://www.so.com/"; 117 | String responseBody; 118 | 119 | responseBody = restTemplate.getForObject(url, String.class); 120 | logger.info("responseBody={}", responseBody); 121 | 122 | HttpHeaders headers = new HttpHeaders(); 123 | headers.setContentType(MediaType.APPLICATION_JSON_UTF8); 124 | HttpEntity> requestEntity = new HttpEntity<>(null,headers); 125 | responseBody = restTemplate.postForObject(url, requestEntity, String.class); 126 | logger.info("responseBody={}", responseBody); 127 | } 128 | 129 | ``` -------------------------------------------------------------------------------- /spring-cloud/swagger/swagger和zuul的结合.md: -------------------------------------------------------------------------------- 1 | # 概述 2 | 3 | 这里讲一下在cloud项目中如何基于zuul实现所有的服务都能在一个swagger文档中的具体实现。具体的项目目录在如下的目录中:https://github.com/wuxiaobo000111/java-framework/tree/master/spring-cloud-group/swagger-group 4 | 5 | 6 | 具体的就不写了啊,直接把项目拉下来看看就行了。 -------------------------------------------------------------------------------- /spring-cloud/zuul/Zuul入门教程.md: -------------------------------------------------------------------------------- 1 | # 概述 2 | 3 | >    这里给出一个zuul的最简单的例子。大概涉及到了两个项目。[zuul-eureka-server-demo](https://github.com/wuxiaobo000111/java-framework/tree/master/spring-cloud-group/zuul-group/zuul-eureka-server-demo "zuul-eureka-server-demo")和[zuul-eureka-server-demo](https://github.com/wuxiaobo000111/java-framework/tree/master/spring-cloud-group/zuul-group/zuul-eureka-server-demo "zuul-eureka-server-demo") 4 | 5 | 6 | # 接口 7 | 8 | # POM依赖 9 | 10 | ```xml 11 | 12 | 13 | org.springframework.boot 14 | spring-boot-starter-web 15 | 16 | 17 | org.springframework.cloud 18 | spring-cloud-starter-netflix-eureka-client 19 | 20 | 21 | 22 | org.springframework.boot 23 | spring-boot-starter-test 24 | test 25 | 26 | 27 | ``` 28 | 29 | ## 配置文件 30 | 31 | ```yml 32 | server: 33 | port: 8103 34 | eureka: 35 | client: 36 | serviceUrl: 37 | defaultZone: http://192.168.88.128:8761/eureka/,http://192.168.88.128:8760/eureka/ 38 | spring: 39 | application: 40 | name: zuul-eureka-client-demo 41 | 42 | 43 | ``` 44 | 45 | ## 启动类 46 | 47 | ```java 48 | package com.bobo.springcloud.learn.zuuleurekaclientdemo; 49 | 50 | import org.springframework.boot.SpringApplication; 51 | import org.springframework.boot.autoconfigure.SpringBootApplication; 52 | import org.springframework.cloud.client.discovery.EnableDiscoveryClient; 53 | 54 | @SpringBootApplication 55 | @EnableDiscoveryClient 56 | public class ZuulEurekaClientDemoApplication { 57 | 58 | public static void main(String[] args) { 59 | SpringApplication.run(ZuulEurekaClientDemoApplication.class, args); 60 | } 61 | 62 | } 63 | 64 | ``` 65 | 66 | ## 接口 67 | 68 | ```java 69 | package com.bobo.springcloud.learn.zuuleurekaclientdemo.controller; 70 | 71 | import org.springframework.web.bind.annotation.GetMapping; 72 | import org.springframework.web.bind.annotation.RestController; 73 | 74 | @RestController 75 | public class HelloController { 76 | 77 | @GetMapping(value = "/hello") 78 | public String hello (String name) { 79 | return "hello, "+ name; 80 | } 81 | } 82 | 83 | ``` 84 | 85 | 86 | # zuul服务 87 | 88 | ## pom依赖 89 | 90 | ```xml 91 | 92 | 93 | org.springframework.cloud 94 | spring-cloud-starter-netflix-eureka-client 95 | 96 | 97 | org.springframework.cloud 98 | spring-cloud-starter-netflix-zuul 99 | 100 | 101 | 102 | org.springframework.boot 103 | spring-boot-starter-test 104 | test 105 | 106 | 107 | 108 | ``` 109 | 110 | ## 配置文件 111 | 112 | ```yml 113 | server: 114 | port: 8102 115 | eureka: 116 | client: 117 | serviceUrl: 118 | defaultZone: http://192.168.88.128:8761/eureka/,http://192.168.88.128:8760/eureka/ 119 | spring: 120 | application: 121 | name: zuul-eureka-server-demo 122 | zuul: 123 | routes: 124 | client-a: 125 | path: /client/** 126 | serviceId: zuul-eureka-client-demo 127 | 128 | 129 | ``` 130 | 131 | >    zuul.routes:指定了路由规则。这里配置的意思是说在访问路径中/client/的所有请求都会映射到zuul-eureka-client-demo这个服务上。 132 | 133 | 134 | ## 启动类 135 | 136 | ```java 137 | 138 | package com.bobo.springcloud.learn.zuuleurekaserverdemo; 139 | 140 | import org.springframework.boot.SpringApplication; 141 | import org.springframework.boot.autoconfigure.SpringBootApplication; 142 | import org.springframework.cloud.client.discovery.EnableDiscoveryClient; 143 | import org.springframework.cloud.netflix.zuul.EnableZuulProxy; 144 | 145 | @SpringBootApplication 146 | @EnableDiscoveryClient 147 | @EnableZuulProxy 148 | public class ZuulEurekaServerDemoApplication { 149 | 150 | public static void main(String[] args) { 151 | SpringApplication.run(ZuulEurekaServerDemoApplication.class, args); 152 | } 153 | 154 | } 155 | 156 | ``` 157 | # 结果 158 | 159 | >    启动两个项目。然后通过访问http://127.0.0.1:8102/client/hello?name=wuxiaobo。就会获取到自己想要的结果。 160 | 161 | -------------------------------------------------------------------------------- /spring-cloud/zuul/Zuul功能.md: -------------------------------------------------------------------------------- 1 | # Zuul饥饿加载 2 | 3 | ```yml 4 | zuul: 5 | routes: 6 | client-a: 7 | path: /client/** 8 | serviceId: zuul-eureka-gray-zuul-client 9 | #开启zuul的饥饿加载 10 | ribbon: 11 | eager-load: 12 | enabled: false 13 | ``` 14 | 15 | ## 请求体修改 16 | 17 | ```java 18 | 19 | public class ModifyRequestEntityFilter extends ZuulFilter { 20 | 21 | @Override 22 | public String filterType() { 23 | return PRE_TYPE; 24 | } 25 | 26 | @Override 27 | public int filterOrder() { 28 | return PRE_DECORATION_FILTER_ORDER + 1;// =6 29 | } 30 | 31 | @Override 32 | public boolean shouldFilter() { 33 | return true; 34 | } 35 | 36 | @Override 37 | public Object run() throws ZuulException { 38 | RequestContext ctx = RequestContext.getCurrentContext(); 39 | HttpServletRequest request = ctx.getRequest(); 40 | request.getParameterMap(); 41 | Map> requestQueryParams = ctx.getRequestQueryParams(); 42 | if (requestQueryParams == null){ 43 | requestQueryParams = new HashMap<>(); 44 | } 45 | //这里添加新增参数的value,注意,只取list的0位 46 | ArrayList arrayList = new ArrayList<>(); 47 | arrayList.add("1wwww"); 48 | requestQueryParams.put("test", arrayList); 49 | ctx.setRequestQueryParams(requestQueryParams); 50 | return null; 51 | } 52 | } 53 | 54 | ``` 55 | > 在网上找到一篇比较好的博客。关于使用Zuul对参数进行加密的可以看一下哦。https://www.cnblogs.com/yuki67/p/9561525.html 56 | 57 | # 使用OkHttp 58 | 59 | ```xml 60 | 61 | com.squareup.okhttp3 62 | okhttp 63 | 3.11.0 64 | 65 | ``` 66 | 67 | ```yml 68 | ribbon: 69 | okhttp: 70 | enabled: true 71 | http: 72 | client: 73 | enabled: false 74 | ``` 75 | 76 | # 开启重试机制 77 | 78 | 79 | ```xml 80 | 81 | 82 | org.springframework.retry 83 | spring-retry 84 | 1.2.2.RELEASE 85 | 86 | ``` 87 | 88 | ```yml 89 | zuul: 90 | routes: 91 | client-a: 92 | path: /client/** 93 | serviceId: zuul-eureka-gray-zuul-client 94 | retryable: true 95 | ribbon: 96 | #重试机制配置 97 | ConnectTimeout: 3000 98 | ReadTimeout: 60000 99 | MaxAutoRetries: 1 #对第一次请求的服务的重试次数 100 | MaxAutoRetriesNextServer: 1 #要重试的下一个服务的最大数量(不包括第一个服务) 101 | OkToRetryOnAllOperations: true 102 | ``` 103 | # Header传递 104 | 105 | 106 | ```java 107 | public class HeaderDeliverFilter extends ZuulFilter { 108 | 109 | @Override 110 | public String filterType() { 111 | return PRE_TYPE; 112 | } 113 | 114 | @Override 115 | public int filterOrder() { 116 | return PRE_DECORATION_FILTER_ORDER + 1;// =6 117 | } 118 | 119 | @Override 120 | public boolean shouldFilter() { 121 | return true; 122 | } 123 | 124 | @Override 125 | public Object run() throws ZuulException { 126 | RequestContext context = RequestContext.getCurrentContext(); 127 | context.addZuulRequestHeader("result", "to next service"); 128 | return null; 129 | } 130 | } 131 | ``` 132 | 133 | # 集合Swagger 134 | 135 | ```xml 136 | 137 | io.springfox 138 | springfox-swagger-ui 139 | 2.7.0 140 | 141 | 142 | io.springfox 143 | springfox-swagger2 144 | 2.7.0 145 | 146 | ``` 147 | 148 | ```java 149 | @Configuration 150 | @EnableSwagger2 151 | public class SwaggerConfig { 152 | 153 | @Autowired 154 | ZuulProperties properties; 155 | 156 | @Primary 157 | @Bean 158 | public SwaggerResourcesProvider swaggerResourcesProvider() { 159 | return () -> { 160 | List resources = new ArrayList<>(); 161 | properties.getRoutes().values().stream() 162 | .forEach(route -> resources.add(createResource(route.getServiceId(), route.getServiceId(), "2.0"))); 163 | return resources; 164 | }; 165 | } 166 | 167 | private SwaggerResource createResource(String name, String location, String version) { 168 | SwaggerResource swaggerResource = new SwaggerResource(); 169 | swaggerResource.setName(name); 170 | swaggerResource.setLocation("/" + location + "/v2/api-docs"); 171 | swaggerResource.setSwaggerVersion(version); 172 | return swaggerResource; 173 | } 174 | 175 | } 176 | ``` -------------------------------------------------------------------------------- /spring-cloud/zuul/Zuul动态路由.md: -------------------------------------------------------------------------------- 1 | 下面的内容来自于《重新定义Spring Cloud一书》 2 | 3 | # 解决动态路由的两种方式 4 | 5 | ```text 6 | 1.结合Spring Cloud Config + Bus,动态刷新配置文件。这种方式的好处是不用Zuul维护映射规则,可以随时修改, 7 | 随时生效;唯一不好的地方是需要单独集成一些使用并不频繁的组件, Config没有可视化界面,维护起规则来也相对麻烦。 8 | 2.重写Zul的配置读取方式,采用事件刷新机制,从数据库读取路由映射规则。此种方式因为基于数据库,可轻松实现管理界面 9 | ,灵活度较高。 10 | ``` 11 | 12 | ## 动态原理实现解析 13 | 14 | ![在这里插入图片描述](https://github.com/wuxiaobo000111/pictures/blob/master/2019-05-10/31.png?raw=true) 15 | 16 | >    locateRoutes()方法继承自SimpleRouteLocator类并重写了规则,该方法主要的功能就是将配置文件中的映射规则信息包装成LinkedHashMap,键String是路径path,值ZuulRoute是配置文件的封装类,以往所见的配置映射读取进来就是使用ZuulRoute来封装。refresh()实现自RefreshableRouteLocator接口,添加刷新功能必须要实现此方法,doRefresh)方法来自SimpleRouteLocator类。我们需要自定义路由配置加载器,以仿照它的实现。其中locateRoutes的结果如下所示 17 | 18 | ![在这里插入图片描述](https://github.com/wuxiaobo000111/pictures/blob/master/2019-05-10/32.jpg?raw=true) 19 | 20 | 21 | 22 | >    SimpleRouteLocator是DiscoveryClientRouteLocator的父类,此类基本实现了RouteLocator接口,对读取的配置文件信息做一些基本处理,提供方法doRefresh()与locateRoutes()供子类实现刷新策略与映射规则加载策略,两个方法源码及签名如下: 23 | 24 | ```java 25 | 26 | /** 27 | * Calculate all the routes and set up a cache for the values. Subclasses can call 28 | * this method if they need to implement {@link RefreshableRouteLocator}. 29 | */ 30 | protected void doRefresh() { 31 | this.routes.set(locateRoutes()); 32 | } 33 | 34 | /** 35 | * Compute a map of path pattern to route. The default is just a static map from the 36 | * {@link ZuulProperties}, but subclasses can add dynamic calculations. 37 | * @return map of Zuul routes 38 | */ 39 | protected Map locateRoutes() { 40 | LinkedHashMap routesMap = new LinkedHashMap<>(); 41 | for (ZuulRoute route : this.properties.getRoutes().values()) { 42 | routesMap.put(route.getPath(), route); 43 | } 44 | return routesMap; 45 | } 46 | ``` 47 | 48 | >    如果要调用doRefresh就需要自己去实现RefreshableRouteLocator接口。 49 | 50 | >    ZuulServerAutoConfiguration注册过滤器和监听器等其他功能。Zuul在注册中心新增服务后刷新监听器也是在此注册的。 51 | 52 | ```java 53 | private static class ZuulRefreshListener 54 | implements ApplicationListener { 55 | 56 | @Autowired 57 | private ZuulHandlerMapping zuulHandlerMapping; 58 | 59 | private HeartbeatMonitor heartbeatMonitor = new HeartbeatMonitor(); 60 | 61 | @Override 62 | public void onApplicationEvent(ApplicationEvent event) { 63 | if (event instanceof ContextRefreshedEvent 64 | || event instanceof RefreshScopeRefreshedEvent 65 | || event instanceof RoutesRefreshedEvent 66 | || event instanceof InstanceRegisteredEvent) { 67 | reset(); 68 | } 69 | else if (event instanceof ParentHeartbeatEvent) { 70 | ParentHeartbeatEvent e = (ParentHeartbeatEvent) event; 71 | resetIfNeeded(e.getValue()); 72 | } 73 | else if (event instanceof HeartbeatEvent) { 74 | HeartbeatEvent e = (HeartbeatEvent) event; 75 | resetIfNeeded(e.getValue()); 76 | } 77 | } 78 | 79 | private void resetIfNeeded(Object value) { 80 | if (this.heartbeatMonitor.update(value)) { 81 | reset(); 82 | } 83 | } 84 | 85 | private void reset() { 86 | this.zuulHandlerMapping.setDirty(true); 87 | } 88 | 89 | } 90 | ``` 91 | 92 | >    由方法onApplicationEvent(ApplicationEvent event)可知, Zul会接收3种事件ContextRefreshedEvent,RefreshScopeRefreshedEvent, RoutesRefreshedEvent通知去刷新路由映射配置信息,此外心跳续约监视器HeartbeatMonitor也会触发这个动作。 93 | 94 | 95 | >    在ZuulHandlerMapping中有一个很重的dirty属性。 96 | 97 | ```java 98 | public void setDirty(boolean dirty) { 99 | this.dirty = dirty; 100 | if (this.routeLocator instanceof RefreshableRouteLocator) { 101 | ((RefreshableRouteLocator) this.routeLocator).refresh(); 102 | } 103 | } 104 | 105 | ``` 106 | 107 | >     这个dirty属性很重要,它是用来控制当前是否需要重新加载映射配置信息的标记,在Zuul每次进行路由操作的时候都会检查这个值,如果为true,就会触发配置信息的重新加载,同时再将其回设为false。也由setDirty(boolean dirty)可知,启动刷新动作必须要实现RefreshableRouteLocator接口。 108 |     本节讲解了路由映射规则的加载原理以及Zuul的事件刷新方式。我们在构建动态路由的时候,只需要重写SimpleRouteLocator类的locateRoutes()方法,并且实现RefreshableRouteLocator接口的refresh)方法,再在内部调用SimpleRouteLocator类的doRefresh)方法,就可以构建起一个由Zul内部事件触发的自定义动态路由加载器。如果不想使用内部事件触发配置更新操作,改为手动触发,可以重写onApplicationEvent(ApplicationEvent event)方法内部实现方式,事实上手动触发的控制性更好。 109 | -------------------------------------------------------------------------------- /spring-cloud/zuul/Zuul权限.md: -------------------------------------------------------------------------------- 1 | # OAuth2.0+JWT 2 | 3 | # 跳过 -------------------------------------------------------------------------------- /spring-cloud/zuul/Zuul灰度发布.md: -------------------------------------------------------------------------------- 1 | # 灰度发布 2 | 3 | >    在Eureka中存在两种元数据。 4 | 5 | ```text 6 | 1. 标准元数据:存放的是服务的各种注册信息。比如IP、端口、服务健康状况。存储在专门为服务开辟的注册表中。 7 | 8 | 2. 自定义元数据: 使用eureka.instance.metadata-map.=value的方式来配置。在内部维持了一个Map来保存自定义的元数据信息。可以配置在远端服务,随着服务一并注册到Eureka注册表中。 9 | 10 | server: 11 | port: 8102 12 | eureka: 13 | client: 14 | serviceUrl: 15 | defaultZone: http://192.168.88.128:8761/eureka/,http://192.168.88.128:8760/eureka/ 16 | instance: 17 | metadata-map: 18 | name: bobo 19 | ``` 20 | 21 | # 代码 22 | 23 | >    参照[zuul-eureka-gray-zuul-client](https://github.com/wuxiaobo000111/java-framework/tree/master/spring-cloud-group/zuul-group/zuul-eureka-gray-zuul-client "zuul-eureka-gray-zuul-client")和[zuul-eureka-gray-zuul-server](https://github.com/wuxiaobo000111/java-framework/tree/master/spring-cloud-group/zuul-group/zuul-eureka-gray-zuul-server "zuul-eureka-gray-zuul-server")两个项目的代码实现 -------------------------------------------------------------------------------- /spring-cloud/zuul/Zuul限流.md: -------------------------------------------------------------------------------- 1 | # 限流算法 2 | 3 | ## 漏桶 4 | 5 | >    当我们的请求或者具有一定体量的数据流涌来的时候,在漏桶的作用下,流量被整形,不能满足要求的部分被削减掉,所以,漏桶算法能够强制限定流量速率。注意,在我们的应用中,这部分溢出的流量是可以被利用起来的,并非完全丢弃,我们可以把它们收集到一个队列里面,做流量排队,尽量做到合理利用所有资源。 6 | 7 | ## 令牌桶 8 | 9 | 10 | >    令牌桶算法的原理是系统会以一个恒定的速度往桶里放入令牌,而如果请求需要被处理,则需要先从桶里获取一个令牌,当桶里没有令牌可取时,则拒绝服务。从原理上看,令牌桶算法和漏桶算法是相反的,一个“进水”,一个是“漏水”。 11 | 12 |     如果想要了解的更多,我在网上找了一篇博客写的很好。https://www.jianshu.com/p/c02899c30bbd 13 | 14 | 15 | ## Zuul如何实现限流 16 | 17 | https://github.com/wuxiaobo000111/spring-cloud-zuul-ratelimit 18 | 19 | -------------------------------------------------------------------------------- /spring-cloud/备注.md: -------------------------------------------------------------------------------- 1 | # 注意 2 | >这个项目集是基于spring cloud 2.0.8 release版本,需要注意一下。 -------------------------------------------------------------------------------- /spring-cloud/链路监控/Sleuth基本术语.md: -------------------------------------------------------------------------------- 1 | # Sleuth的基本术语 2 | 3 | ```text 4 | Span:基本工作单元,例如,在一个新建的span中发送一个RPC等同于发送一个回应请求给RPC,span通过 5 | 一个64位ID唯一标识,trace以另一个64位ID表示,span还有其他数据信息,比如摘要、时间戳事件、关键 6 | 值注释(tags)、span的ID、以及进度ID(通常是IP地址) span在不断的启动和停止,同时记录了时间信息 7 | ,当你创建了一个span,你必须在未来的某个时刻停止它。 8 | 9 | Trace:一系列spans组成的一个树状结构,例如,如果你正在跑一个分布式大数据工程,你可能需要创建一个trace。 10 | 11 | Annotation:用来及时记录一个事件的存在,一些核心annotations用来定义一个请求的开始和结束 12 | cs - Client Sent -客户端发起一个请求,这个annotion描述了这个span的开始 13 | 14 | sr - Server Received -服务端获得请求并准备开始处理它,如果将其sr减去cs时间戳便可得到网络延迟 15 | 16 | ss - Server Sent -注解表明请求处理的完成(当请求返回客户端),如果ss减去sr时间戳便可得 17 | 到服务端需要的处理请求时间 18 | 19 | cr - Client Received -表明span的结束,客户端成功接收到服务端的回复,如果cr减去cs时间戳 20 | 便可得到客户端从服务端获取回复的所有所需时间 21 | ``` 22 | 23 | # 如何部署zipkin 24 | 25 | >   去这个网址上下载https://dl.bintray.com/openzipkin/maven/io/zipkin/java/zipkin-server/2.9.4/。然后使用java -jar命令启动。 26 | 27 | -------------------------------------------------------------------------------- /spring-cloud/链路监控/Sleuth基本用法.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wuxiaobo000111/Java--apollo/53e9cd051a69da77f23bfe7927cf87085c356c18/spring-cloud/链路监控/Sleuth基本用法.md -------------------------------------------------------------------------------- /spring-cloud/链路监控/skywalking介绍.md: -------------------------------------------------------------------------------- 1 | 很多东西都来自skywalking的官方文档。所以大家有时间可以去访问以下skywalking的github地址。https://github.com/apache/skywalking 2 | 3 | # 为什么需要链路追踪 4 | 5 |     随着微服务架构的流行,一些微服务架构下的问题也会越来越突出,比如一个请求会涉及多个服务,而服务本身可能也会依赖其他服务,整个请求路径就构成了一个网状的调用链,而在整个调用链中一旦某个节点发生异常,整个调用链的稳定性就会受到影响,所以会深深的感受到 “银弹” 这个词是不存在的,每种架构都有其优缺点 。 6 | 7 | 8 |     面对以上情况, 我们就需要一些可以帮助理解系统行为、用于分析性能问题的工具,以便发生故障的时候,能够快速定位和解决问题,这时候 APM(应用性能管理)工具就该闪亮登场了。 9 | 10 | # skyWalking架构设计 11 | 12 | ![在这里插入图片描述](https://github.com/wuxiaobo000111/pictures/blob/master/2019-05-10/81.png?raw=true) 13 | 14 | SkyWalking在逻辑上分为四个部分:探针,平台后端,存储和UI。 15 | 16 | ```text 17 | 18 | 探针 基于不同的来源是不同的。它们收集数据并根据SkyWalking需求重新格式化。 19 | 20 | 平台后端 是一个后端服务,支持集群部署。它用于聚合,分析和驱动从探针到UI的流程。它还提供可插拔能力为不同 21 | 的数据源(如Zipkin)、存储器实现和集群管理。您甚至可以使用观察分析语言自定义聚合和分析。 22 | 23 | 存储 是开放的。您可以选择现有的实现,例如ElasticSearch、H2或Sharding-Sphere管理的MySQL集群,也可以实现 24 | 自己的。当然,非常感谢您为新的存储实现做出贡献。 25 | 26 | UI 酷炫且非常强大,你也可以自己定制UI匹配后端。 27 | ``` 28 | 29 | # 如何搭建SkyWalking -------------------------------------------------------------------------------- /spring/Spring注册解析的bean.md: -------------------------------------------------------------------------------- 1 | # 注册bean 2 | 3 | >    使用的是BeanDefinitionReaderUtils.registerBeanDefinition()完成注册。BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());方法中有两个参数。BeanDefinitionHolder表示的是bean的信息。BeanDefinitionRegistry表示是bean要注册的类。是通过上下文获取的。registerBeanDefinition()方法通过两种方式实现注册bean。一种是通过beanName注册。一种是通过别名注册。 4 | 5 | ```java 6 | public static void registerBeanDefinition( 7 | BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry) 8 | throws BeanDefinitionStoreException { 9 | 10 | // Register bean definition under primary name. 11 | String beanName = definitionHolder.getBeanName(); 12 | registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition()); 13 | 14 | // Register aliases for bean name, if any. 15 | String[] aliases = definitionHolder.getAliases(); 16 | if (aliases != null) { 17 | for (String alias : aliases) { 18 | registry.registerAlias(beanName, alias); 19 | } 20 | } 21 | } 22 | ``` 23 | 24 | # 通过beanName实现注册 25 | 26 | >    这个方法是DefaultListableBeanFactory类中的实现。 27 | 28 | ```java 29 | public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition) 30 | throws BeanDefinitionStoreException { 31 | 32 | Assert.hasText(beanName, "Bean name must not be empty"); 33 | Assert.notNull(beanDefinition, "BeanDefinition must not be null"); 34 | 35 | if (beanDefinition instanceof AbstractBeanDefinition) { 36 | //对BeanDefinition中的MethodOverrides进行校验。 37 | try { 38 | ((AbstractBeanDefinition) beanDefinition).validate(); 39 | } 40 | catch (BeanDefinitionValidationException ex) { 41 | throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName, 42 | "Validation of bean definition failed", ex); 43 | } 44 | } 45 | 46 | //根据BeanName判断BeanDefinition是否存在。 47 | 48 | BeanDefinition existingDefinition = this.beanDefinitionMap.get(beanName); 49 | if (existingDefinition != null) { 50 | if (!isAllowBeanDefinitionOverriding()) { 51 | throw new BeanDefinitionOverrideException(beanName, beanDefinition, existingDefinition); 52 | } 53 | else if (existingDefinition.getRole() < beanDefinition.getRole()) { 54 | // e.g. was ROLE_APPLICATION, now overriding with ROLE_SUPPORT or ROLE_INFRASTRUCTURE 55 | if (logger.isInfoEnabled()) { 56 | logger.info("Overriding user-defined bean definition for bean '" + beanName + 57 | "' with a framework-generated bean definition: replacing [" + 58 | existingDefinition + "] with [" + beanDefinition + "]"); 59 | } 60 | } 61 | else if (!beanDefinition.equals(existingDefinition)) { 62 | if (logger.isDebugEnabled()) { 63 | logger.debug("Overriding bean definition for bean '" + beanName + 64 | "' with a different definition: replacing [" + existingDefinition + 65 | "] with [" + beanDefinition + "]"); 66 | } 67 | } 68 | else { 69 | if (logger.isTraceEnabled()) { 70 | logger.trace("Overriding bean definition for bean '" + beanName + 71 | "' with an equivalent definition: replacing [" + existingDefinition + 72 | "] with [" + beanDefinition + "]"); 73 | } 74 | } 75 | this.beanDefinitionMap.put(beanName, beanDefinition); 76 | } 77 | else { 78 | if (hasBeanCreationStarted()) { 79 | // Cannot modify startup-time collection elements anymore (for stable iteration) 80 | synchronized (this.beanDefinitionMap) { 81 | this.beanDefinitionMap.put(beanName, beanDefinition); 82 | List updatedDefinitions = new ArrayList<>(this.beanDefinitionNames.size() + 1); 83 | updatedDefinitions.addAll(this.beanDefinitionNames); 84 | updatedDefinitions.add(beanName); 85 | this.beanDefinitionNames = updatedDefinitions; 86 | if (this.manualSingletonNames.contains(beanName)) { 87 | Set updatedSingletons = new LinkedHashSet<>(this.manualSingletonNames); 88 | updatedSingletons.remove(beanName); 89 | this.manualSingletonNames = updatedSingletons; 90 | } 91 | } 92 | } 93 | else { 94 | // Still in startup registration phase 95 | this.beanDefinitionMap.put(beanName, beanDefinition); 96 | this.beanDefinitionNames.add(beanName); 97 | this.manualSingletonNames.remove(beanName); 98 | } 99 | this.frozenBeanDefinitionNames = null; 100 | } 101 | 102 | if (existingDefinition != null || containsSingleton(beanName)) { 103 | resetBeanDefinition(beanName); 104 | } 105 | } 106 | ``` -------------------------------------------------------------------------------- /spring/Spring自定义标签解析.md: -------------------------------------------------------------------------------- 1 | # 概述 2 | 3 | >    在DefaultBeanDefinitionDocumentReader.parseBeanDefinitions()方法中调用了BeanDefinitionParserDelegate.parseCustomElement用来解析配置文件中的自定义标签。首先我们来介绍一下如何自定义标签。 4 | 5 | # Spring中如何自定义标签 6 | 7 | # 自定义标签的步骤 8 | 9 | 10 | ```text 11 | 1.创建一个需要扩展的组件. 12 | 2.定义一个XSD文件描述组件内容。 13 | 3.创建一个文件,实现BeanDefinitionParser接口,用来解析XSD文件中的定义和组件定义。 14 | 4.创建一个Handler文件,扩展自NamespaceHandlerSupport, 目的是将组件注册到Spring容器。 15 | 5.编写Spring.handlers和Spring.schemas文件。 16 | 17 | ``` -------------------------------------------------------------------------------- /spring/Spring默认标签中的自定义标签解析.md: -------------------------------------------------------------------------------- 1 | 2 | # 概述 3 | 4 | >    在DefaultBeanDefinitionDocumentReader.processBeanDefinition方法中调用了bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);用来解析默认标签中的自定义标签。我们可以看一下这个方法中都实现了什么: 5 | 6 | ```java 7 | //类名是BeanDefinitionParserDelegate 8 | public BeanDefinitionHolder decorateBeanDefinitionIfRequired(Element ele, BeanDefinitionHolder definitionHolder) { 9 | return decorateBeanDefinitionIfRequired(ele, definitionHolder, null); 10 | } 11 | 12 | public BeanDefinitionHolder decorateBeanDefinitionIfRequired( 13 | Element ele, BeanDefinitionHolder definitionHolder, @Nullable BeanDefinition containingBd) { 14 | 15 | BeanDefinitionHolder finalDefinition = definitionHolder; 16 | 17 | // Decorate based on custom attributes first. 18 | NamedNodeMap attributes = ele.getAttributes(); 19 | for (int i = 0; i < attributes.getLength(); i++) { 20 | Node node = attributes.item(i); 21 | finalDefinition = decorateIfRequired(node, finalDefinition, containingBd); 22 | } 23 | 24 | // Decorate based on custom nested elements. 25 | NodeList children = ele.getChildNodes(); 26 | for (int i = 0; i < children.getLength(); i++) { 27 | Node node = children.item(i); 28 | if (node.getNodeType() == Node.ELEMENT_NODE) { 29 | finalDefinition = decorateIfRequired(node, finalDefinition, containingBd); 30 | } 31 | } 32 | return finalDefinition; 33 | } 34 | 35 | public BeanDefinitionHolder decorateIfRequired( 36 | Node node, BeanDefinitionHolder originalDef, @Nullable BeanDefinition containingBd) { 37 | //获取自定义标签的Uri 38 | String namespaceUri = getNamespaceURI(node); 39 | if (namespaceUri != null && !isDefaultNamespace(namespaceUri)) { 40 | NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri); 41 | if (handler != null) { 42 | BeanDefinitionHolder decorated = 43 | handler.decorate(node, originalDef, new ParserContext(this.readerContext, this, containingBd)); 44 | if (decorated != null) { 45 | return decorated; 46 | } 47 | } 48 | else if (namespaceUri.startsWith("http://www.springframework.org/")) { 49 | error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", node); 50 | } 51 | else { 52 | // A custom namespace, not to be handled by Spring - maybe "xml:...". 53 | if (logger.isDebugEnabled()) { 54 | logger.debug("No Spring NamespaceHandler found for XML schema namespace [" + namespaceUri + "]"); 55 | } 56 | } 57 | } 58 | return originalDef; 59 | } 60 | ``` -------------------------------------------------------------------------------- /spring/spring核心类.md: -------------------------------------------------------------------------------- 1 | # DefaultListableBeanFactory 2 | 3 | >     XmlBeanFactory继承自DefaultListableBeanFactory,而DefaultListableBeanFactory是整个bean加载的核心部分,是Spring注册及加载bean的默认实现,而对于XmlBeanfactory与DefaultListableBeanFactory不同的地方其实是在XmlBeanFactory中使用了自定义的XML读取器,XmlBeanDefinitionReader,实现了个性化的BeanDefinitionReader读取, DefaultListableBeanFactory继承了AbstractAutowireCapableBeanFactory并实现了ConfigurableListableBeanFactory以及BeanDefinitionRegistry接口。以下是ConfigurableListableBeanFactory的继承关系。 4 | 5 | ![在这里插入图片描述](https://github.com/wuxiaobo000111/pictures/blob/master/2019-05-10/1.jpg?raw=true) 6 | 7 | 8 | >     如下的列表中展现了类的作用。 9 | 10 | | 类名| 作用| 11 | |--|--| 12 | | | | 13 | |AliasRegistry|定义对alias的简单增删改等操作.| 14 | |SimpleAliasRegistry|主要使用map作为alias的缓存,并对接口AliasRegistry进行实现。| 15 | |SingletonBeanRegistry|定义对单例的注册及获取,| 16 | |BeanFactory|定义获取bean及bean的各种属性。| 17 | |DefaultSingletonBeanRegistry|对接口SingletonBeanRegistry各函数的实现.| 18 | |HierarchicalBeanFactory|继承BeanFactory,也就是在BeanFactory定义的功能的基础上增加了对parentFactory的支持| 19 | |BeanDefinitionRegistry|定义对BeanDefinition的各种增删改操作。| 20 | |FactoryBeanRegistrySupport|在DefaultSingletonBeanRegistry基础上增加了对FactoryBean的特殊处理功能。| 21 | |ConfigurableBeanFactory|提供配置Factory的各种方法。| 22 | |ListableBeanFactory|根据各种条件获取bean的配置清单。| 23 | |AbstractBeanFactory|综合FactoryBeanRegistrySupport和ConfigurableBeanFactory 的功能| 24 | |AutowireCapableBeanFactory|提供创建bean、 自动注入、初始化以及应用bean的后处理器,| 25 | |AbstractAutowireCapableBeanFactory|综合AbstractBeanFactory并对接口Autowire CapableBeanFactory进行实现.| 26 | |ConfigurableListableBeanFactory|Beanfactory配置清单,指定忽略类型及接口等.DefaultListableBeanFactory:综合上面所有功能,主要是对Bean注册后的处理。| 27 | 28 | 29 | >    XmlBeanFactory使用XmlBeanDefinitionReader对配置文件进行解析,读取其中的bean。 30 | 31 | # XmlBeanDefinitionReader 32 | 33 | >     XML配置文件的读取是Spring中重要的功能,因为Spring的大部分功能都是以配置作为切入点的,那么我们可以从XmlBeanDefinitionReader中梳理一下资源文件读取、解析及注册的大致脉络,首先我们看看各个类的功能。 34 | 35 | | 类名 | 类名 | 36 | |--|--| 37 | | ResourceLoader| 定义资源加载器,主要应用于根据给定的资源文件地址返回对应的Resource.| 38 | | BeanDefinitionReader|主要定义资源文件读取并转换为BeanDefinition的各个功能。| 39 | |EnvironmentCapable|定义获取Environment方法,| 40 | |DocumentLoader|定义从资源文件加载到转换为Document的功能,| 41 | |AbstractBeanDefinitionReader|对EnvironmentCapable, BeanDefinitionReader类定义的功能进行实现。| 42 | |BeanDefinitionDocumentReader|定义读取Docuemnt并注册BeanDefinition功能.| 43 | |BeanDefinitionParserDelegate|定义解析Element的各种方法。| 44 | 45 | >    经过以上分析,我们可以梳理出整个XML配置文件读取的大致流程,如图2-6所示,在XmlBeanDifinitionReader中主要包含以下几步的处理: 46 | 47 | ``` 48 | (1)通过继承自AbstractBeanDefinitionReader中的方法,来使用ResourLoader将资源文件路径转换为对应的Resource文件。 49 | (2)通过DocumentLoader对Resource文件进行转换,将Resource文件转换为Docurment文件。 50 | (3)通过实现接口BeanDefinitionDocumentReader的DefaultBeanDefinitionDocumentReader类对Document进行解析, 51 | 并使用BeanDefinitionParserDelegate对Element进行解析。 52 | ``` 53 | -------------------------------------------------------------------------------- /spring/配置文件的封装.md: -------------------------------------------------------------------------------- 1 | 2 | # ClassPathResource 3 | 4 | >Spring中使用ClassPathResource对配置文件进行了封装。继承关系如下所示: 5 | 6 | ![在这里插入图片描述](https://github.com/wuxiaobo000111/pictures/blob/master/2019-05-10/2.jpg?raw=true) -------------------------------------------------------------------------------- /web/HTTP和HTTPS.md: -------------------------------------------------------------------------------- 1 | # 什么是HTTP 2 | 3 | >    超文本传输协议,是一个基于请求与响应,无状态的,应用层的协议,常基于TCP/IP协议传输数据,互联网上应用最为广泛的一种网络协议,所有的WWW文件都必须遵守这个标准。设计HTTP的初衷是为了提供一种发布和接收HTML页面的方法。 4 | 5 | | 版本|生产时间 |内容 |发展状态 | 6 | |-|-|-|-| 7 | |HTTP/0.9 |1991年 |不涉及数据包传输,规定客户端和服务器之间通信格式,只能GET请求 |没有作为正式的标准 | 8 | | HTTP/1.0| 1996年| 传输内容格式不限制,增加PUT、PATCH、HEAD、 OPTIONS、DELETE命令|正式作为标准 | 9 | |HTTP/1.1 |1997年 | 持久连接(长连接)、节约带宽、HOST域、管道机制、分块传输编码|2015年前使用最广泛 | 10 | |HTTP/2 | 2015年|多路复用、服务器推送、头信息压缩、二进制协议等 | 逐渐覆盖市场| 11 | 12 | ![](https://github.com/wuxiaobo000111/pictures/blob/master/2019-03-17/10.png?raw=true "") 13 | 14 | >    这个Akamai公司建立的一个官方的演示,使用HTTP/1.1和HTTP/2同时请求379张图片,观察请求的时间,明显看出HTTP/2性能占优势。 15 | 16 | ![](https://github.com/wuxiaobo000111/pictures/blob/master/2019-03-17/11.jpg?raw=true "") 17 | 18 | 19 | >    多路复用:通过单一的HTTP/2连接请求发起多重的请求-响应消息,多个请求stream共享一个TCP连接,实现多留并行而不是依赖建立多个TCP连接。 20 | 21 | # HTTP的特点 22 | ```text 23 | 1.无状态:协议对客户端没有状态存储,对事物处理没有“记忆”能力,比如访问一个网站需要反复进行登录操作 24 | 2.无连接:HTTP/1.1之前,由于无状态特点,每次请求需要通过TCP三次握手四次挥手,和服务器重新建立连接。比如某个客户机在短时间多 25 | 次请求同一个资源,服务器并不能区别是否已经响应过用户的请求,所以每次需要重新响应请求,需要耗费不必要的时间和流量。 26 | 3.基于请求和响应:基本的特性,由客户端发起请求,服务端响应 27 | 4.简单快速、灵活 28 | 5.通信使用明文、请求和响应不会对通信方进行确认、无法保护数据的完整性 29 | ``` 30 | 31 | # HTTP通信传输 32 | ![](https://github.com/wuxiaobo000111/pictures/blob/master/2019-03-18/1.jpg?raw=true "") 33 | 34 | >    客户端输入URL回车,DNS解析域名得到服务器的IP地址,服务器在80端口监听客户端请求,端口通过TCP/IP协议(可以通过Socket实现)建立连接。HTTP属于TCP/IP模型中的运用层协议,所以通信的过程其实是对应数据的入栈和出栈。 35 | 36 | ![](https://github.com/wuxiaobo000111/pictures/blob/master/2019-03-18/2.jpg?raw=true "") 37 | 38 | ![](https://github.com/wuxiaobo000111/pictures/blob/master/2019-03-18/3.jpg?raw=true "") 39 | 40 | >    为什么需要三次握手呢?为了防止已失效的连接请求报文段突然又传送到了服务端,因而产生错误。比如:client发出的第一个连接请求报文段并没有丢失,而是在某个网络结点长时间的滞留了,以致延误到连接释放以后的某个时间才到达server。本来这是一个早已失效的报文段,但是server收到此失效的连接请求报文段后,就误认为是client再次发出的一个新的连接请求,于是就向client发出确认报文段,同意建立连接。假设不采用“三次握手”,那么只要server发出确认,新的连接就建立了,由于client并没有发出建立连接的请求,因此不会理睬server的确认,也不会向server发送数据,但server却以为新的运输连接已经建立,并一直等待client发来数据。所以没有采用“三次握手”,这种情况下server的很多资源就白白浪费掉了。 41 | 42 | ![](https://github.com/wuxiaobo000111/pictures/blob/master/2019-03-18/4.jpg?raw=true "") 43 | 44 | 45 | >    为什么需要四次挥手呢?TCP是全双工模式,当client发出FIN报文段时,只是表示client已经没有数据要发送了,client告诉server,它的数据已经全部发送完毕了;但是,这个时候client还是可以接受来server的数据;当server返回ACK报文段时,表示它已经知道client没有数据发送了,但是server还是可以发送数据到client的;当server也发送了FIN报文段时,这个时候就表示server也没有数据要发送了,就会告诉client,我也没有数据要发送了,如果收到client确认报文段,之后彼此就会愉快的中断这次TCP连接。 46 | 47 | 48 | # 什么是HTTPS 49 | 50 | >    HTTPS是身披SSL外壳的HTTP。HTTPS是一种通过计算机网络进行安全通信的传输协议,经由HTTP进行通信,利用SSL/TLS建立全信道,加密数据包。HTTPS使用的主要目的是提供对网站服务器的身份认证,同时保护交换数据的隐私与完整性。 51 | PS:TLS是传输层加密协议,前身是SSL协议,由网景公司1995年发布,有时候两者不区分。 52 | 53 | # HTTPS的特点 54 | ```text 55 | 1.内容加密:采用混合加密技术,中间者无法直接查看明文内容 56 | 2.验证身份:通过证书认证客户端访问的是自己的服务器 57 | 3.保护数据完整性:防止传输的内容被中间人冒充或者篡改 58 | ``` 59 | 60 | # HTTP实现原理 61 | 62 | 63 | ![](https://github.com/wuxiaobo000111/pictures/blob/master/2019-03-18/5.jpg?raw=true "") 64 | 65 | ```text 66 | client向server发送请求https://baidu.com,然后连接到server的443端口。 67 | 68 | 服务端必须要有一套数字证书,可以自己制作,也可以向组织申请。区别就是自己颁发的证书需要客户端验证通过,才可以继续访问,而使用受信任的公司申请的证书则不会弹出提示页面,这套证书其实就是一对公钥和私钥。 69 | 70 | 传送证书 71 | 这个证书其实就是公钥,只是包含了很多信息,如证书的颁发机构,过期时间、服务端的公钥,第三方证书认证机构(CA)的签名,服务端的域名信息等内容。 72 | 73 | 客户端解析证书 74 | 这部分工作是由客户端的TLS来完成的,首先会验证公钥是否有效,比如颁发机构,过期时间等等,如果发现异常,则会弹出一个警告框,提示证书存在问题。如果证书没有问题,那么就生成一个随即值(秘钥)。然后用证书对该随机值进行加密。 75 | 76 | 传送加密信息 77 | 这部分传送的是用证书加密后的秘钥,目的就是让服务端得到这个秘钥,以后客户端和服务端的通信就可以通过这个随机值来进行加密解密了。 78 | 79 | 服务段加密信息 80 | 服务端用私钥解密秘密秘钥,得到了客户端传过来的私钥,然后把内容通过该值进行对称加密。 81 | 82 | 传输加密后的信息 83 | 这部分信息是服务端用私钥加密后的信息,可以在客户端被还原。 84 | 85 | 客户端解密信息 86 | 客户端用之前生成的私钥解密服务端传过来的信息,于是获取了解密后的内容。 87 | 88 | ``` 89 | 90 | >    其实总结一下就是首先通过非对称加密的手段,把客户端生成的一个对称加密的秘钥发送到服务端,服务端通过私钥解密得到这个对称加密的秘钥,然后客户端和服务端是通过对称加密进行通信的。 91 | -------------------------------------------------------------------------------- /数据结构/B树.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | >注意:这里是从视频上看到的。原来的视频地址是https://www.bilibili.com/video/av36069871/?p=1 4 | 5 | ![在这里插入图片描述](https://github.com/wuxiaobo000111/pictures/blob/master/2019-03-15/2.jpg?raw=true) 6 | ![在这里插入图片描述](https://github.com/wuxiaobo000111/pictures/blob/master/2019-03-15/3.jpg?raw=true) 7 | ![在这里插入图片描述](https://github.com/wuxiaobo000111/pictures/blob/master/2019-03-15/4.jpg?raw=true) 8 | ![在这里插入图片描述](https://github.com/wuxiaobo000111/pictures/blob/master/2019-03-15/5.jpg?raw=true) 9 | ![在这里插入图片描述](https://github.com/wuxiaobo000111/pictures/blob/master/2019-03-15/6.jpg?raw=true) 10 | ![在这里插入图片描述](https://github.com/wuxiaobo000111/pictures/blob/master/2019-03-15/7.jpg?raw=true) 11 | ![在这里插入图片描述](https://github.com/wuxiaobo000111/pictures/blob/master/2019-03-15/8.jpg?raw=true) 12 | ![在这里插入图片描述](https://github.com/wuxiaobo000111/pictures/blob/master/2019-03-15/9.jpg?raw=true) 13 | 14 | ![在这里插入图片描述](https://github.com/wuxiaobo000111/pictures/blob/master/2019-03-16/1.jpg?raw=true) 15 | 16 | ![在这里插入图片描述](https://github.com/wuxiaobo000111/pictures/blob/master/2019-03-16/2.jpg?raw=true) 17 | 18 | ![在这里插入图片描述](https://github.com/wuxiaobo000111/pictures/blob/master/2019-03-16/3.jpg?raw=true) 19 | 20 | ![在这里插入图片描述](https://github.com/wuxiaobo000111/pictures/blob/master/2019-03-16/4.jpg?raw=true) 21 | 22 | ![在这里插入图片描述](https://github.com/wuxiaobo000111/pictures/blob/master/2019-03-16/5.jpg?raw=true) 23 | 24 | ![在这里插入图片描述](https://github.com/wuxiaobo000111/pictures/blob/master/2019-03-16/6.jpg?raw=true) 25 | 26 | ![在这里插入图片描述](https://github.com/wuxiaobo000111/pictures/blob/master/2019-03-16/7.jpg?raw=true) 27 | 28 | ![在这里插入图片描述](https://github.com/wuxiaobo000111/pictures/blob/master/2019-03-16/8.jpg?raw=true) 29 | 30 | -------------------------------------------------------------------------------- /数据结构/排序/扩展:三种交换数字的方式.md: -------------------------------------------------------------------------------- 1 | 2 | ```java 3 | 4 | import java.util.Arrays; 5 | 6 | /** 7 | * Created by zhoujunfu on 2018/9/10. 8 | */ 9 | public class SwapDemo { 10 | 11 | public static void main(String[] args) { 12 | // 临时变量法 13 | int[] array = new int[]{10, 20}; 14 | System.out.println(Arrays.toString(array)); 15 | swapByTemp(array, 0, 1); 16 | System.out.println(Arrays.toString(array)); 17 | 18 | // 算术法 19 | array = new int[]{10, 20}; 20 | swapByArithmetic(array, 0, 1); 21 | System.out.println(Arrays.toString(array)); 22 | 23 | // 位运算法 24 | array = new int[]{10, 20}; 25 | swapByBitOperation(array, 0, 1); 26 | System.out.println(Arrays.toString(array)); 27 | } 28 | 29 | /** 30 | * 通过临时变量交换数组array的i和j位置的数据 31 | * @param array 数组 32 | * @param i 下标i 33 | * @param j 下标j 34 | */ 35 | public static void swapByTemp(int[] array, int i, int j) { 36 | int temp = array[i]; 37 | array[i] = array[j]; 38 | array[j] = temp; 39 | } 40 | 41 | /** 42 | * 通过算术法交换数组array的i和j位置的数据(有可能溢出) 43 | * @param array 数组 44 | * @param i 下标i 45 | * @param j 下标j 46 | */ 47 | public static void swapByArithmetic(int[] array, int i, int j) { 48 | array[i] = array[i] + array[j]; 49 | array[j] = array[i] - array[j]; 50 | array[i] = array[i] - array[j]; 51 | } 52 | 53 | 54 | /** 55 | * 通过位运算法交换数组array的i和j位置的数据 56 | * @param array 数组 57 | * @param i 下标i 58 | * @param j 下标j 59 | */ 60 | public static void swapByBitOperation(int[] array, int i, int j) { 61 | array[i] = array[i]^array[j]; 62 | array[j] = array[i]^array[j]; //array[i]^array[j]^array[j]=array[i] 63 | array[i] = array[i]^array[i]; //array[i]^array[j]^array[i]=array[j] 64 | } 65 | } 66 | 67 | 复制代码 68 | ``` -------------------------------------------------------------------------------- /数据结构/红黑树.md: -------------------------------------------------------------------------------- 1 | # 红黑树特征 2 | 3 | ```text 4 | 1.节点是红色或者是黑色。 5 | 2.根节点一定是黑色。 6 | 3.每个叶子节点都是黑色的空节点(NIL节点)。 7 | 4.每个红节点的两个子节点都是黑色的。 8 | 5.从任一节点到其每个叶子节点的所有路径都包含相同数目的黑色节点。 9 | ``` 10 | 11 | ![在这里插入图片描述](https://github.com/wuxiaobo000111/pictures/blob/master/2019-03-21/3.png?raw=true) 12 | -------------------------------------------------------------------------------- /架构之路/代码质量管理平台SonarQube/代码质量管理平台SonarQube.md: -------------------------------------------------------------------------------- 1 | > https://www.cnblogs.com/qiumingcheng/p/7253917.html 2 | 3 | 4 | # 概述 5 | 6 | >    SonarQube是管理代码质量一个开放平台,可以快速的定位代码中潜在的或者明显的错误,下面将会介绍一下这个工具的安装、配置以及使用。准备工作; 7 | 8 | ```text 9 | 1、jdk(不再介绍) 10 | 11 | 2、sonarqube:http://www.sonarqube.org/downloads/ 12 | 13 | 3、SonarQube+Scanner:https://sonarsource.bintray.com/Distribution/sonar-scanner-cli/sonar-scanner-2.5.zip 14 | 15 | 4、mysql数据库(不再介绍) 16 | ``` 17 | 18 | # 安装篇 19 | 20 | >    1.下载好sonarqube后,解压打开bin目录,启动相应OS目录下的StartSonar。如本文演示使用的是win的64位系统,则打开D:\sonar\sonarqube-5.3\sonarqube-5.3\bin\windows-x86-64\StartSonar.bat 21 | >    启动浏览器,访问http://localhost:9000,如出现下图则表示安装成功。 22 | 23 | ![在这里插入图片描述](https://github.com/wuxiaobo000111/pictures/blob/master/2019-05-10/47.png?raw=true) 24 | 25 | 26 | # 配置篇 27 | 28 | >    打开mysql,新建一个数据库。 29 | 30 | >    打开sonarqube安装目录下的D:\sonar\sonarqube-5.3\sonarqube-5.3\conf\sonar.properties文件 31 | 32 | >    在mysql5.X节点下输入以下信息 33 | 34 | ```properties 35 | sonar.jdbc.url=jdbc:mysql://172.16.30.228:3306/qjfsonar?useUnicode=true&characterEncoding=utf8&rewriteBatchedStatements=true&useConfigs=maxPerformance 36 | sonar.jdbc.username=gmsd 37 | sonar.jdbc.password=gmsdtrade 38 | sonar.sorceEncoding=UTF-8 39 | sonar.login=admin 40 | sonar.password=admin 41 | 43 | ``` 44 | 45 | >    重启sonarqube服务,再次访问http://localhost:9000,会稍微有点慢,因为要初始化数据库信息 46 | 47 | >    数据库初始化成功后,登录 48 | 49 | >    按照下图的点击顺序,进入插件安装页面 50 | 51 | ![在这里插入图片描述](https://github.com/wuxiaobo000111/pictures/blob/master/2019-05-10/48.png?raw=true) 52 | 53 | >    7.搜索chinese Pack,安装中文语言包 54 | 55 | ![在这里插入图片描述](https://github.com/wuxiaobo000111/pictures/blob/master/2019-05-10/49.png?raw=true) 56 | 57 | >    安装成功后,重启sonarqube服务,再次访问http://localhost:9000/,即可看到中文界面 58 | 59 | ![在这里插入图片描述](https://github.com/wuxiaobo000111/pictures/blob/master/2019-05-10/50.png?raw=true) 60 | 61 | 62 | # 使用篇 63 | 64 | >    打开D:\sonar\sonar-scanner-2.5\conf\sonar-runner.properties文件 65 | 66 | >    mysql节点下输入以下信息 67 | 68 | ```properties 69 | sonar.jdbc.url=jdbc:mysql://172.16.30.228:3306/qjfsonar?useUnicode=true&characterEncoding=utf8&rewriteBatchedStatements=true&useConfigs=maxPerformance 70 | sonar.jdbc.username=gmsd 71 | sonar.jdbc.password=gmsdtrade 72 | 73 | 注意:如果测试项目与服务器不在同一台机子,则需要添加服务器的IP: 74 | 75 | #----- Default SonarQube server 76 | sonar.host.url=http://XXX.XXX.XXX.XXX:9000 77 | ``` 78 | >    配置环境变量 79 | 80 | ```text 81 | a.新建变量,name=SONAR_RUNNER_HOME。value=D:\sonar\sonar-scanner-2.5 82 | 83 | b.打开path,输入%SONAR_RUNNER_HOME%\bin; 84 | 85 | c.sonar-runner -version,出现以下信息,则表示环境变量设置成功 86 | ``` 87 | 88 | ![在这里插入图片描述](https://github.com/wuxiaobo000111/pictures/blob/master/2019-05-10/51.png?raw=true) 89 | 90 | >    4.打开要进行代码分析的项目根目录,新建sonar-project.properties文件 91 | 92 | >    5.输入以下信息 93 | 94 | ```text 95 | 96 | # must be unique in a given SonarQube instance 97 | sonar.projectKey=my:project 98 | # this is the name displayed in the SonarQube UI 99 | sonar.projectName=apiautocore 100 | sonar.projectVersion=1.0 101 | 102 | # Path is relative to the sonar-project.properties file. Replace "\" by "/" on Windows. 103 | # Since SonarQube 4.2, this property is optional if sonar.modules is set. 104 | # If not set, SonarQube starts looking for source code from the directory containing 105 | # the sonar-project.properties file. 106 | sonar.sources=src 107 | 108 | # Encoding of the source code. Default is default system encoding 109 | #sonar.sourceEncoding=UTF-8 110 | 111 | 112 | 其中:projectName是项目名字,sources是源文件所在的目录 113 | ``` 114 | 115 | >    6.设置成功后,启动sonarqube服务,并启动cmd 116 | 117 | >    7.在cmd进入项目所在的根目录,输入命令:sonar-runner,分析成功后会出现下图 118 | 119 | ![在这里插入图片描述](https://github.com/wuxiaobo000111/pictures/blob/master/2019-05-10/52.png?raw=true) 120 | 121 | >    打开http://localhost:9000/,我们会看到主页出现了分析项目的概要图 122 | 123 | ![在这里插入图片描述](https://github.com/wuxiaobo000111/pictures/blob/master/2019-05-10/53.png?raw=true) 124 | 125 | >    我们点击项目,选择问题链接,会看到分析代码的bug,哇,好多 126 | 127 | ![在这里插入图片描述](https://github.com/wuxiaobo000111/pictures/blob/master/2019-05-10/54.png?raw=true) -------------------------------------------------------------------------------- /面试题/Spring/SpringBean循环创建.md: -------------------------------------------------------------------------------- 1 | # Spring Bean循环创建问题 2 | 3 | 4 | # 什么是循环依赖 5 | 6 | 循环依赖,其实就是循环引用,就是两个或者两个以上的 bean 互相引用对方,最终形成一个闭环,如 A 依赖 B,B 依赖 C,C 依赖 A。如下图所示:
![](https://cdn.nlark.com/yuque/0/2019/jpeg/382109/1561459547654-eddd8429-936e-49f6-a6d4-70591b11792b.jpeg#align=left&display=inline&height=249&originHeight=249&originWidth=308&size=0&status=done&width=308)
循环依赖,其实就是一个**死循环**的过程,在初始化 A 的时候发现引用了 B,这时就会去初始化 B,然后又发现 B 引用 C,跑去初始化 C,初始化 C 的时候发现引用了 A,则又会去初始化 A,依次循环永不退出,除非有**终结条件**。
Spring 循环依赖的**场景**有两种: 7 | 8 | 1. 构造器的循环依赖。 9 | 1. field 属性的循环依赖。 10 | 11 | 对于构造器的循环依赖,Spring 是无法解决的,只能抛出 BeanCurrentlyInCreationException 异常表示循环依赖,**所以下面我们分析的都是基于 field 属性的循环依赖**。
12 | 13 | 14 | 15 | # 小结 16 | 17 | - Spring 在创建 bean 的时候并不是等它完全完成,而是在创建过程中将创建中的 bean 的 ObjectFactory 提前曝光(即加入到 `singletonFactories` 缓存中)。 18 | - 这样,一旦下一个 bean 创建的时候需要依赖 bean ,则直接使用 ObjectFactory 的 `#getObject()` 方法来获取了。 19 | 20 | 到这里,关于 Spring 解决 bean 循环依赖就已经分析完毕了。最后来描述下就上面那个循环依赖 Spring 解决的过程: 21 | 22 | - 首先 A 完成初始化第一步并将自己提前曝光出来(通过 ObjectFactory 将自己提前曝光),在初始化的时候,发现自己依赖对象 B,此时就会去尝试 get(B),这个时候发现 B 还没有被创建出来 23 | - 然后 B 就走创建流程,在 B 初始化的时候,同样发现自己依赖 C,C 也没有被创建出来 24 | - 这个时候 C 又开始初始化进程,但是在初始化的过程中发现自己依赖 A,于是尝试 get(A),这个时候由于 A 已经添加至缓存中(一般都是添加至三级缓存 `singletonFactories` ),通过 ObjectFactory 提前曝光,所以可以通过 `ObjectFactory#getObject()` 方法来拿到 A 对象,C 拿到 A 对象后顺利完成初始化,然后将自己添加到一级缓存中 25 | - 回到 B ,B 也可以拿到 C 对象,完成初始化,A 可以顺利拿到 B 完成初始化。到这里整个链路就已经完成了初始化过程了 26 | 27 | ![](https://cdn.nlark.com/yuque/0/2019/png/382109/1561459836009-77912b1f-dcde-4ad1-bbe2-412146f35c19.png#align=left&display=inline&height=970&originHeight=970&originWidth=2126&size=0&status=done&width=2126) 28 | -------------------------------------------------------------------------------- /面试题/Zookeeper/Zookeeper命令.md: -------------------------------------------------------------------------------- 1 | # Zookeeper命令 2 | 3 | 4 | # connect 5 | 连接zk服务端,与close命令配合使用可以连接或者断开zk服务端。如connect 127.0.0.1:2181 6 | 7 | 8 | # get 9 | 获取节点信息,注意节点的路径皆为绝对路径,也就是说必要要从/(根路径)开始。
![image.png](https://cdn.nlark.com/yuque/0/2019/png/382109/1561026638389-b9dd57e3-2402-4ea4-971c-95776cd5f8a8.png#align=left&display=inline&height=367&name=image.png&originHeight=404&originWidth=981&size=106317&status=done&width=891.8181624885437) 10 | 11 | ``` 12 | hello world为节点数据信息 13 | cZxid节点创建时的zxid 14 | ctime节点创建时间 15 | mZxid节点最近一次更新时的zxid 16 | mtime节点最近一次更新的时间 17 | cversion子节点数据更新次数 18 | dataVersion本节点数据更新次数 19 | aclVersion节点ACL(授权信息)的更新次数 20 | ephemeralOwner如果该节点为临时节点,ephemeralOwner值表示与该节点绑定的session id. 21 | 如果该节点不是临时节点,ephemeralOwner值为0 22 | dataLength节点数据长度,本例中为hello world的长度 23 | numChildren子节点个数 24 | ``` 25 | 26 | 27 | # ls 28 | 获取路径下的节点信息,注意此路径为绝对路径,类似于linux的ls命令。如ls /zookeeper 29 | 30 | 31 | # set 32 | 设置节点的数据。如set /zookeeper "hello world" 33 | 34 | 35 | # rmr 36 | 删除节点命令,此命令与delete命令不同的是delete不可删除有子节点的节点,但是rmr命令可以删除,注意路径为绝对路径。如rmr /zookeeper/znode 37 | 38 | 39 | # setquota 40 | 设置子节点个数和数据长度配额。如setquota –n 4 /zookeeper/node 设置/zookeeper/node子节点个数最大为4,setquota –b 100 /zookeeper/node 设置/zookeeper/node节点长度最大为100 41 | 42 | 43 | # listquota 44 | 显示配额。 45 | 46 | ``` 47 | listquota /zookeeper 48 | absolute path is/zookeeper/quota/zookeeper/zookeeper_limits 49 | Output quota for /zookeepercount=2,bytes=-1 50 | 51 | 解释:/zookeeper节点个数限额为2,长度无限额。 52 | ``` 53 | 54 | 55 | 56 | # quit 57 | 退出。 58 | 59 | 60 | # printwatches 61 | 设置和显示监视状态,on或者off。如printwatches on 62 | 63 | 64 | # create 65 | 创建节点,其中-s为顺序充点,-e临时节点。如create /zookeeper/node1"test_create" world:anyone:cdrwa。其中acl处,请参见getAcl和setAcl命令。 66 | 67 | 68 | # stat 69 | 查看节点状态信息。如stat / 70 | 71 | 72 | # close 73 | > 断开客户端与服务端的连接。 74 | > 75 | 76 | 77 | # [](#ls2命令)ls2 78 | > ls2为ls命令的扩展,比ls命令多输出本节点信息。 79 | > 80 | 81 | 82 | # [](#history命令)history 83 | > 列出最近的历史命令。 84 | > 85 | 86 | 87 | # [](#setacl命令)setAcl 88 | 设置节点Acl。此处重点说一下acl,acl由大部分组成:1为scheme,2为user,3为permission,一般情况下表示为scheme:id:permissions。其中scheme和id是相关的,下面将scheme和id一起说明。
scheme和id 89 | 90 | - world: 它下面只有一个id, 叫anyone, world:anyone代表任何人,zookeeper中对所有人有权限的结点就是属于world:anyone的 91 | - auth: 它不需要id, 只要是通过authentication的user都有权限(zookeeper支持通过kerberos来进行authencation, 也支持username/password形式的authentication) 92 | - digest: 它对应的id为username:BASE64(SHA1(password)),它需要先通过username:password形式的authentication 93 | - ip: 它对应的id为客户机的IP地址,设置的时候可以设置一个ip段,比如ip:192.168.1.0/16, 表示匹配前16个bit的IP段 94 | - super: 在这种scheme情况下,对应的id拥有超级权限,可以做任何事情(cdrwa) 95 | 96 | permissions 97 | 98 | - CREATE(c): 创建权限,可以在在当前node下创建child node 99 | - DELETE(d): 删除权限,可以删除当前的node 100 | - READ(r): 读权限,可以获取当前node的数据,可以list当前node所有的child nodes 101 | - WRITE(w): 写权限,可以向当前node写数据 102 | - ADMIN(a): 管理权限,可以设置当前node的permission 103 | 104 | 综上,一个简单使用setAcl命令,则可以为:
setAcl /zookeeper/node1 world:anyone:cdrw
105 | 106 | 107 | # [](#getacl命令)getAcl 108 | 获取节点Acl。如getAcl /zookeeper/node1 'world,'anyone: cdrwa 注:可参见setAcl命令。
109 | 110 | 111 | # [](#sync命令)sync 112 | 强制同步。如sync /zookeeper,由于请求在半数以上的zk server上生效就表示此请求生效,那么就会有一些zk server上的数据是旧的。sync命令就是强制同步所有的更新操作。
113 |
114 | 115 | 116 | # [](#redo命令)redo 117 | 再次执行某命令。如redo 10.其中10为命令ID,需与history配合使用。
118 | 119 | 120 | # [](#addauth命令)addauth 121 | 节点认证。如addauth digest username:password,可参见setAcl命令digest处。 122 | ``` 123 | 使用方法: 124 | 一、通过setAcl设置用户名和密码 125 | setAcl pathdigest:username:base64(sha1(password)):crwda 126 | 二、认证 127 | addauth digest username:password 128 | ``` 129 | 130 | ### 131 | 132 | # delete 133 | 删除节点。如delete /zknode1 134 | 135 | # 136 |
137 | 138 | # 139 | --------------------------------------------------------------------------------