├── .gitignore ├── LeetCode ├── README.md ├── SlidingWindow │ └── 209-MinimumSizeSubarraySum.md ├── code │ └── source │ │ ├── ContainerWithMostWater.java │ │ ├── IntegerToRoman.java │ │ ├── LongestCommonPrefix.java │ │ ├── LongestPalindromicSubstring.java │ │ ├── MedianOfTwoSortedArrays.java │ │ ├── N_209_MinimumSizeSubarraySum.java │ │ ├── PalindromeNumber.java │ │ ├── RegularExpressionMatching.java │ │ ├── RomanToInteger.java │ │ ├── StringToIntegerAtoi.java │ │ ├── ThreeSum.java │ │ ├── addTwoNumbers.java │ │ ├── convert.java │ │ ├── isValid.java │ │ ├── lengthOfLongestSubstring.java │ │ ├── maxSubArray.java │ │ ├── mergeTwoLists.java │ │ ├── removeDuplicates.java │ │ ├── reverse.java │ │ ├── struct │ │ └── ListNode.java │ │ └── twoSum.java ├── solution │ └── leetcode │ │ ├── 1.两数之和.md │ │ ├── 10.正则表达式匹配.md │ │ ├── 11.盛最多水的容器.md │ │ ├── 12.整数转罗马数字.md │ │ ├── 13.罗马数字转整数.md │ │ ├── 14.最长公共前缀.md │ │ ├── 15.三数之和.md │ │ ├── 2.两数相加.md │ │ ├── 20.有效的括号.md │ │ ├── 21.合并两个有序链表.md │ │ ├── 26.删除排序数组中的重复项.md │ │ ├── 3.无重复字符的最长子串.md │ │ ├── 4.两个排序数组的中位数.md │ │ ├── 5.最长回文子串.md │ │ ├── 53.最大子序和.md │ │ ├── 6.Z字型变化.md │ │ ├── 7.反转整数.md │ │ ├── 8.字符串转整数(atoi).md │ │ └── 9.回文数.md ├── 排序算法 │ ├── 1356_根据数字二进制下 1 的数目排序.md │ ├── 1370_上升下降字符串.md │ ├── 1502_判断能否形成等差数列.md │ ├── 1528_重新排列字符串.md │ ├── 349_两个数组的交集.md │ ├── 350_两个数组的交集_II.md │ ├── 922_按奇偶排序数组_II.md │ └── 计数排序,基数排序和桶排序.md ├── 数据结构 │ └── 二叉树 │ │ ├── 2236-判断根结点是否等于子结点之和.md │ │ ├── 993-二叉树的堂兄弟节点.md │ │ ├── s11020707042022.png │ │ └── s11341507042022.png ├── 链表 │ ├── 21_合并两个有序链表.md │ └── 82_删除排序链表中的重复元素_II.md └── 队列 │ ├── 703_数据流中的第K大元素.md │ └── 优先队列.md ├── Program └── 工具篇 │ └── Yapi │ ├── Dockerfile │ ├── config.json │ ├── docker-compose.yml │ ├── docker-entrypoint.sh │ ├── download.sh │ ├── yapi.tgz │ └── 使用DockerCompose构建部署Yapi.md ├── README.md ├── 剑指Offer ├── 1_斐波那契数列 │ └── README.md ├── 2_跳台阶问题 │ └── README.md ├── 3_二维数组中的查找 │ └── README.md ├── 4_空格的替换 │ └── README.md ├── 5_用两个栈实现队列 │ └── README.md └── README.md └── 研发相关 ├── Bitcoin核心 └── 什么是Merkle树.md ├── DevOps ├── CI-CD │ ├── README.md │ ├── 从0到1,完整的CI-CD流程.md │ ├── 使用Docker-Compose组合多个镜像.md │ ├── 使用Jenkins实现自动部署.md │ ├── 使用Maven和WinSCP命令自动打包上传.md │ └── 提交镜像到DockerHub.md ├── 敏捷开发 │ ├── Swagger下的前后端协作.md │ ├── 使用Worktile来打造高效的团队.md │ ├── 程序员的自我修养.md │ └── 编写可读代码的艺术.md └── 测试 │ └── JMeter压测.md ├── JAVA基础 ├── JVM │ ├── Java内存模型(JMM).md │ ├── 内存分析.md │ ├── 深入理解JVM:内存区域.md │ ├── 深入理解JVM:垃圾回收.md │ └── 深入理解JVM:类加载机制.md ├── 其他 │ └── Java的值传递.md ├── 多线程 │ ├── ThreadLocal的使用及实现.md │ ├── volatile关键字的作用和原理.md │ ├── 创建线程的三种方式及其对比.md │ ├── 同步锁,乐观锁,悲观锁.md │ ├── 线程复用:线程池.md │ └── 锁优化策略.md ├── 设计模式 │ └── 常用的设计模式.md └── 集合 │ ├── ArrayList、Vector、LinkedList分析.md │ ├── HashMap与HashTable.md │ ├── 深入分析LinkedHashMap.md │ └── 深入理解ConcurrentHashMap.md ├── K8s └── core │ └── 一些命令.md ├── README.md ├── Spring ├── Springboot │ ├── HikariCP配置详解+多数据源.md │ ├── README.md │ ├── Spring-cloud-feign添加统一的Header.md │ ├── SpringBoot2.x-Actutor-micrometer-自定义Mertics.md │ ├── SpringBoot2.x-定时任务.md │ ├── SpringBoot2.x中使用Actuator来做应用监控.md │ ├── SpringBoot2.x中的应用监控:Actuator+Prometheus+Grafana.md │ ├── SpringBoot2.x中的应用监控:Actuator+InFluxDB+Telegraf+Grafana.md │ ├── SpringBoot获取配置文件属性.md │ ├── Springboot-2.x-脚手架项目.md │ └── 使用Swagger来进行前后端协作.md └── 核心 │ ├── Spring核心接口Ordered的实现及应用.md │ └── 深入理解Spring AOP.md ├── 中间件 ├── Kafka │ ├── Kafka-Streams-Wiondowing.md │ ├── Kafka处理脏读和幻读.md │ ├── Kafka如何保证消息不丢失,且只被消费一次.md │ ├── Kafka添加Header.md │ ├── Mac安装Kafka.md │ ├── README.md │ ├── Springboot+Kafka.md │ ├── 命令.md │ └── 流式Json数据生成器.md ├── 分布式环境下保持数据一致性.md └── 消息队列之事务消息.md ├── 分布式系统 ├── README.md ├── 分布式事务 │ ├── BASE理论.md │ ├── CAP原则.md │ ├── SAGA事务模型.md │ ├── TCC事务模型.md │ ├── XA协议与二阶段提交.md │ ├── 事务消息.md │ ├── 分布式事务总览.md │ ├── 分布式环境下保持数据一致性.md │ ├── 刚性事务与柔性事务.md │ └── 本地消息表.md └── 分布式系统调用跟踪 │ ├── Logback.md │ ├── MDC: MappedDiagnosticContexts.md │ ├── OpenTracing规范.md │ ├── Sleuth代码解析.md │ ├── 一个简单的OpenTracing实现.md │ ├── 使用MDC实现日志链路跟踪.md │ └── 分布式链路跟踪.md ├── 区块链 ├── OpenZeppelin集成:编写健壮安全的智能合约.md ├── README.md ├── Truffle框架和Ganache本地私链.md ├── 从0开始完成DApp(一)-基础篇.md ├── 从0开始完成DApp(三)-实践篇.md ├── 从0开始完成DApp(二)-深入篇.md ├── 以太坊轻钱包MetaMask安装.md └── 使用remix-ide开发以太坊智能合约.md ├── 数据库 ├── MySQL │ ├── Partitioning │ │ ├── 0_分区概述.md │ │ ├── 1_分区类型.md │ │ ├── 2_分区管理.md │ │ ├── 3_分区修剪.md │ │ ├── 4_分区选择.md │ │ └── README.md │ ├── SQL语句 │ │ ├── SQL优化详解(一).md │ │ ├── SQL优化详解(三).md │ │ └── SQL优化详解(二).md │ └── 日常SQL使用 │ │ ├── 导出.md │ │ └── 查看数据库大小.md └── Redis │ └── Redis实战应用场景.md ├── 系统设计 ├── API管理 │ ├── README.md │ └── Yapi │ │ ├── Dockerfile │ │ ├── README.md │ │ ├── config.json │ │ ├── docker-compose.yml │ │ ├── docker-entrypoint.sh │ │ ├── download.sh │ │ └── yapi.tgz ├── CAS │ ├── README.md │ └── SpringBoot-CAS在前后端分离中的实践.md ├── IDE │ ├── README.md │ └── 使用IDEA自带的Editor-REST-Client来测试REST-API.md ├── 好玩的 │ ├── VSCode插件发布.md │ ├── VSCode插件开发.md │ ├── 使用Selenium破解拼图验证码.md │ └── 使用函数计算来构建小程序.md ├── 微服务 │ ├── 0_调用链_Zipkin.md │ └── README.md ├── 日志分析 │ ├── Elasticsearch+Logstash+kibana搭建可视化日志分析平台.md │ └── README.md ├── 流量治理 │ └── 设计一个基于用户的限流策略.md ├── 秒杀系统 │ ├── ESI与CSI.md │ ├── 一个秒杀系统的设计思考.md │ └── 什么是CDN.md ├── 缓存 │ ├── README.md │ ├── SpringBoot缓存之Caffeine.md │ ├── 深入浅出分布式缓存的通用方法.md │ ├── 缓存之Caffeine.md │ ├── 缓存之GuavaCache.md │ ├── 缓存之LFU算法.md │ ├── 缓存之算法探究与实现.md │ └── 缓存穿透、缓存击穿、缓存雪崩.md └── 负载均衡 │ ├── README.md │ └── 现代网络负载均衡与代理.md └── 面试相关 └── 12_28_问题.md /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | ### Example user template template 3 | ### Example user template 4 | 5 | # IntelliJ project files 6 | .idea 7 | *.iml 8 | out 9 | gen 10 | production -------------------------------------------------------------------------------- /LeetCode/README.md: -------------------------------------------------------------------------------- 1 | # Algorithm 2 | 3 | ## LeetCode-Easy 4 | 5 | - [1. 两数之和](/solution/leetcode/1.两数之和.md) 6 | - [7. 反转整数](/solution/leetcode/7.反转整数.md) 7 | - [9. 回文数](/solution/leetcode/9.回文数.md) 8 | - [13. 罗马数字转整数](/solution/leetcode/13.罗马数字转整数.md) 9 | - [53. 最大子序和](/solution/leetcode/53.最大子序和.md) 10 | 11 | 12 | ## LeetCode-Medium 13 | 14 | - [2. 两数相加](/solution/leetcode/2.两数相加.md) 15 | - [3. 无重复字符的最长子串](/solution/leetcode/3.无重复字符的最长子串.md) 16 | - [5. 最长回文子串](/solution/leetcode/5.最长回文子串.md) 17 | - [6. Z字型变化](/solution/leetcode/6.Z字型变化.md) 18 | - [8. 字符串转整数(atoi)](/solution/leetcode/8.字符串转整数(atoi).md) 19 | - [11. 盛最多水的容器](/solution/leetcode/11.盛最多水的容器.md) 20 | - [12. 整数转罗马数字](/solution/leetcode/12.整数转罗马数字.md) 21 | 22 | 23 | 24 | 25 | 26 | ## LeetCode-Hard 27 | 28 | - [4. 两个排序数组的中位数](/solution/leetcode/4.两个排序数组的中位数.md) 29 | 30 | ## Other -------------------------------------------------------------------------------- /LeetCode/SlidingWindow/209-MinimumSizeSubarraySum.md: -------------------------------------------------------------------------------- 1 | # 209 - Minimum Size Subarray Sum 2 | 3 | ## 题目 4 | 5 | 给定一个含有 `n` 个正整数的数组和一个正整数 `s` ,找出该数组中满足其和 `≥s` 的长度最小的连续子数组。如果不存在符合条件的连续子数组,返回 `0` 。 6 | 7 | 示例: 8 | 9 | ``` 10 | 输入: s = 7, nums = [2,3,1,2,4,3] 11 | 输出: 2 12 | 解释: 子数组 [4,3] 是该条件下的长度最小的连续子数组。 13 | ``` 14 | 15 | 进阶: 16 | 17 | 如果你已经完成了 `O(n)` 时间复杂度的解法, 请尝试 `O(n log n)` 时间复杂度的解法。 18 | 19 | ## Solution 1 20 | 21 | > 时间复杂度O(n) 22 | 23 | 定义2个指针 `left` 和 `right` 指针: 24 | 25 | 1. `right` 先右移,直到第一次满足 `sum≥s` 26 | 27 | 2. `left` 开始右移,直到第一次满足 `sum < s` 28 | 29 | 重复上面的步骤,直到 right 到达末尾,且 left 到达临界位置,即要么到达边界,要么再往右移动,和就会小于给定值。 30 | 31 | ``` 32 | public static int solution(int s, int[] nums) { 33 | int left = 0, sum = 0, minLength = Integer.MAX_VALUE; 34 | for (int right = 0; right < nums.length; right++) { 35 | sum += nums[right]; 36 | while (sum >= s) { 37 | minLength = Math.min(minLength, right - left + 1); 38 | sum -= nums[left++]; 39 | } 40 | } 41 | return minLength == Integer.MAX_VALUE ? 0 : minLength; 42 | } 43 | ``` 44 | 45 | ## Solution 2 46 | 47 | > 时间复杂度 O(nlogn) 48 | 49 | 1. 建立一个比原数组长一位的 `sum` 数组,其中 `sum[i]` 表示 `nums` 数组中 `[0, i - 1]` 的和 50 | 51 | 2. 对于 `sum` 中每一个值 `sum[i]`,用二分查找法找到子数组的右边界位置,使该子数组之和大于 `sum[i] + s`,然后更新最短长度的距离 52 | 53 | ``` 54 | public static int solution2(int s, int[] nums) { 55 | int[] sum = new int[nums.length]; 56 | int minLength = Integer.MAX_VALUE; 57 | if (nums.length != 0) { 58 | sum[0] = nums[0]; 59 | } 60 | for (int i = 1; i < nums.length; i++) { 61 | sum[i] = sum[i - 1] + nums[i]; 62 | } 63 | for (int i = 0; i < nums.length; i++) { 64 | if (sum[i] >= s) { 65 | minLength = Math.min(minLength, i - binarySearchLastIndexNotBiggerThanTarget(0, i, sum[i] - s, sum)); 66 | } 67 | } 68 | return minLength == Integer.MAX_VALUE ? 0 : minLength; 69 | } 70 | 71 | static int binarySearchLastIndexNotBiggerThanTarget(int left, int right, int target, int[] sum) { 72 | while (left <= right) { 73 | int mid = (left + right) >> 1; 74 | if (sum[mid] > target) { 75 | right = mid - 1; 76 | } else { 77 | left = mid + 1; 78 | } 79 | } 80 | return right; 81 | } 82 | ``` -------------------------------------------------------------------------------- /LeetCode/code/source/ContainerWithMostWater.java: -------------------------------------------------------------------------------- 1 | public class ContainerWithMostWater { 2 | 3 | public int maxArea(int[] height) { 4 | int max = 0; 5 | int left = 0; 6 | int right = height.length - 1; 7 | while (left < right) { 8 | max = Math.max(max, Math.min(height[left], height[right]) * (right - left)); 9 | if (height[left] < height[right]) { 10 | left++; 11 | } else { 12 | right--; 13 | } 14 | } 15 | return max; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /LeetCode/code/source/IntegerToRoman.java: -------------------------------------------------------------------------------- 1 | public class IntegerToRoman { 2 | 3 | public String intToRoman(int num) { 4 | StringBuilder res = new StringBuilder(); 5 | char roman[] = {'M', 'D', 'C', 'L', 'X', 'V', 'I'}; 6 | int value[] = {1000, 500, 100, 50, 10, 5, 1}; 7 | 8 | for (int i = 0; i < 7; i = i + 2) { 9 | int x = num / value[i]; 10 | if (x < 4) { 11 | for (int j = 0; j < x; j++) { 12 | res.append(roman[i]); 13 | } 14 | } else if (x == 4) { 15 | res.append(roman[i]).append(roman[i - 1]); 16 | } else if (x < 9) { 17 | res.append(roman[i - 1]); 18 | for (int j = 5; j < x; j++) { 19 | res.append(roman[i]); 20 | } 21 | } else { 22 | res.append(roman[i]).append(roman[i - 2]); 23 | } 24 | num = num % value[i]; 25 | } 26 | 27 | return res.toString(); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /LeetCode/code/source/LongestCommonPrefix.java: -------------------------------------------------------------------------------- 1 | public class LongestCommonPrefix { 2 | 3 | public String longestCommonPrefix(String[] strs) { 4 | 5 | if (strs.length == 0) { 6 | return ""; 7 | } 8 | 9 | String s0 = strs[0]; 10 | 11 | for (int i = 0; i < s0.length(); i++) { 12 | char c = s0.charAt(i); 13 | for (int j = 1; j < strs.length; j++) { 14 | if (i == strs[j].length() || strs[j].charAt(i) != c) { 15 | return s0.substring(0, i); 16 | } 17 | } 18 | } 19 | 20 | return s0; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /LeetCode/code/source/LongestPalindromicSubstring.java: -------------------------------------------------------------------------------- 1 | public class LongestPalindromicSubstring { 2 | public String longestPalindrome(String s) { 3 | return ""; 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /LeetCode/code/source/MedianOfTwoSortedArrays.java: -------------------------------------------------------------------------------- 1 | public class MedianOfTwoSortedArrays { 2 | public double findMedianSortedArrays(int[] nums1, int[] nums2) { 3 | return 2D; 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /LeetCode/code/source/N_209_MinimumSizeSubarraySum.java: -------------------------------------------------------------------------------- 1 | public class N_209_MinimumSizeSubarraySum { 2 | 3 | public static int solution(int s, int[] nums) { 4 | int left = 0, sum = 0, minLength = Integer.MAX_VALUE; 5 | for (int right = 0; right < nums.length; right++) { 6 | sum += nums[right]; 7 | while (sum >= s) { 8 | minLength = Math.min(minLength, right - left + 1); 9 | sum -= nums[left++]; 10 | } 11 | } 12 | return minLength == Integer.MAX_VALUE ? 0 : minLength; 13 | } 14 | 15 | public static int solution2(int s, int[] nums) { 16 | int[] sum = new int[nums.length]; 17 | int minLength = Integer.MAX_VALUE; 18 | if (nums.length != 0) { 19 | sum[0] = nums[0]; 20 | } 21 | for (int i = 1; i < nums.length; i++) { 22 | sum[i] = sum[i - 1] + nums[i]; 23 | } 24 | for (int i = 0; i < nums.length; i++) { 25 | if (sum[i] >= s) { 26 | minLength = Math.min(minLength, i - binarySearchLastIndexNotBiggerThanTarget(0, i, sum[i] - s, sum)); 27 | } 28 | } 29 | return minLength == Integer.MAX_VALUE ? 0 : minLength; 30 | } 31 | 32 | static int binarySearchLastIndexNotBiggerThanTarget(int left, int right, int target, int[] sum) { 33 | while (left <= right) { 34 | int mid = (left + right) >> 1; 35 | if (sum[mid] > target) { 36 | right = mid - 1; 37 | } else { 38 | left = mid + 1; 39 | } 40 | } 41 | return right; 42 | } 43 | 44 | public static void main(String[] args) { 45 | int s = 7; 46 | int[] nums = {2, 3, 1, 2, 4, 3}; 47 | System.out.println(solution(s, nums)); 48 | System.out.println(solution2(s, nums)); 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /LeetCode/code/source/PalindromeNumber.java: -------------------------------------------------------------------------------- 1 | public class PalindromeNumber { 2 | 3 | public boolean isPalindrome(int x) { 4 | if (x < 0 || x != 0 && x % 10 == 0) { 5 | return false; 6 | } 7 | int num = 0; 8 | while (x > num) { 9 | num = num * 10 + x % 10; 10 | x = x / 10; 11 | } 12 | return x == num || num / 10 == x; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /LeetCode/code/source/RegularExpressionMatching.java: -------------------------------------------------------------------------------- 1 | public class RegularExpressionMatching { 2 | public boolean isMatch(String s, String p) { 3 | return false; 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /LeetCode/code/source/RomanToInteger.java: -------------------------------------------------------------------------------- 1 | import java.util.HashMap; 2 | import java.util.Map; 3 | 4 | public class RomanToInteger { 5 | 6 | public int romanToInt(String s) { 7 | 8 | Map map = new HashMap<>(); 9 | map.put('I', 1); 10 | map.put('V', 5); 11 | map.put('X', 10); 12 | map.put('L', 50); 13 | map.put('C', 100); 14 | map.put('D', 500); 15 | map.put('M', 1000); 16 | 17 | Integer result = 0; 18 | for (int i = 0; i < s.length(); i++) { 19 | Character c = s.charAt(i); 20 | if (i > 0 && map.get(c) > map.get(s.charAt(i - 1))) { 21 | result = result + map.get(c) - 2 * map.get(s.charAt(i - 1)); 22 | } else { 23 | result = result + map.get(c); 24 | } 25 | } 26 | 27 | return result; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /LeetCode/code/source/StringToIntegerAtoi.java: -------------------------------------------------------------------------------- 1 | public class StringToIntegerAtoi { 2 | 3 | public static int myAtoi(String str) { 4 | 5 | if (str.isEmpty()) { 6 | return 0; 7 | } 8 | 9 | int length = str.length(); 10 | int sign = 1; 11 | int result = 0; 12 | int i = 0; 13 | 14 | while (i < length && str.charAt(i) == ' ') { 15 | i++; 16 | } 17 | 18 | if (i < length && (str.charAt(i) == '-'|| str.charAt(i) == '+')) { 19 | sign = str.charAt(i++) == '-' ? -1 : 1; 20 | } 21 | 22 | while (i < length && str.charAt(i) >= '0' && str.charAt(i) <= '9') { 23 | if (result > Integer.MAX_VALUE / 10 || result == Integer.MAX_VALUE / 10 && str.charAt(i) - '0' > 7) { 24 | return sign == 1 ? Integer.MAX_VALUE : Integer.MIN_VALUE; 25 | } 26 | result = result * 10 + (str.charAt(i++) - '0'); 27 | } 28 | 29 | return result * sign; 30 | } 31 | 32 | public static void main(String[] args) { 33 | System.out.println(myAtoi(" ")); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /LeetCode/code/source/ThreeSum.java: -------------------------------------------------------------------------------- 1 | import java.util.List; 2 | 3 | public class ThreeSum { 4 | public List> threeSum(int[] nums) { 5 | return null; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /LeetCode/code/source/addTwoNumbers.java: -------------------------------------------------------------------------------- 1 | import struct.ListNode; 2 | 3 | public class addTwoNumbers { 4 | 5 | public static ListNode addTwoNumbers(ListNode l1, ListNode l2) { 6 | ListNode temp = null; 7 | ListNode l3 = null; 8 | 9 | int mod = 0; 10 | // 只有满足三种情况才退出计算 11 | while (l1 != null || l2 != null || mod != 0) { 12 | int sum = (l1 == null ? 0 : l1.val) + (l2 == null ? 0 : l2.val) + mod; 13 | mod = sum / 10; 14 | //产生 l3 当前节点 15 | ListNode node = new ListNode(sum % 10); 16 | if (temp == null) { 17 | // 产生 head 节点 18 | temp = node; 19 | l3 = temp; 20 | } else { 21 | temp.next = node; 22 | // 移动指针 23 | temp = temp.next; 24 | } 25 | // l1 l2 指向下一个节点 26 | l1 = l1 == null ? null : l1.next; 27 | l2 = l2 == null ? null : l2.next; 28 | } 29 | return l3; 30 | } 31 | 32 | public static void main(String[] args) { 33 | ListNode l1=new ListNode(2); 34 | ListNode l1N1=new ListNode(4); 35 | ListNode l1N2=new ListNode(3); 36 | l1.next=l1N1; 37 | l1N1.next=l1N2; 38 | 39 | ListNode l2=new ListNode(5); 40 | ListNode l2N1=new ListNode(6); 41 | ListNode l2N2=new ListNode(4); 42 | l2.next=l2N1; 43 | l2N1.next=l2N2; 44 | 45 | System.out.println(addTwoNumbers(l1,l2).val); 46 | System.out.println(addTwoNumbers(l1,l2).next.val); 47 | System.out.println(addTwoNumbers(l1,l2).next.next.val); 48 | 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /LeetCode/code/source/convert.java: -------------------------------------------------------------------------------- 1 | public class convert { 2 | 3 | 4 | public static String convert(String s, int numRows) { 5 | int length = s.length(); 6 | int nodeLength = numRows * 2 - 2; 7 | 8 | if (length == 0 | numRows == 0 | numRows == 1) { 9 | return s; 10 | } 11 | String result = ""; 12 | for (int i = 0; i < numRows; i++) { 13 | for (int j = i; j < length; j += nodeLength) { 14 | // 整列数据直接等差 15 | result += s.charAt(j); 16 | int single = j - 2 * i + nodeLength; 17 | // 去除首行,尾行,且不能超过最长长度 18 | if (i != 0 && i != numRows - 1 && single < length) { 19 | result += s.charAt(single); 20 | } 21 | } 22 | } 23 | 24 | return result; 25 | } 26 | 27 | public static void main(String[] args) { 28 | System.out.println(convert("ABCDEFGHIGKLMNO", 5)); 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /LeetCode/code/source/isValid.java: -------------------------------------------------------------------------------- 1 | public class isValid { 2 | 3 | public boolean isValid(String s) { 4 | return true; 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /LeetCode/code/source/lengthOfLongestSubstring.java: -------------------------------------------------------------------------------- 1 | import java.util.HashMap; 2 | 3 | public class lengthOfLongestSubstring { 4 | 5 | public static int lengthOfLongestSubstring(String s) { 6 | if (s == null || s.length() == 0) { 7 | return 0; 8 | } 9 | 10 | HashMap map = new HashMap<>(); 11 | int max = 0; 12 | int pre = -1; 13 | for (int i = 0, length = s.length(); i < length; i++) { 14 | char ch = s.charAt(i); 15 | Integer chIndex = map.get(ch); 16 | map.put(ch, i); 17 | // 更新重复单词位置 18 | if (chIndex != null) { 19 | pre = Math.max(pre, chIndex); 20 | } 21 | max = Math.max(max, i - pre); 22 | } 23 | 24 | return max; 25 | } 26 | 27 | public static int lengthOfLongestSubstring2(String s) { 28 | 29 | int index[] = new int[128]; 30 | int max = 0; 31 | int pre = 0; 32 | for (int i = 0, length = s.length(); i < length; i++) { 33 | char ch = s.charAt(i); 34 | Integer chIndex = index[ch]; 35 | pre = Math.max(pre, chIndex); 36 | max = Math.max(max, i - pre + 1); 37 | index[ch] = i + 1; 38 | } 39 | 40 | return max; 41 | } 42 | 43 | public static void main(String[] args) { 44 | //System.out.println(lengthOfLongestSubstring("apwwkew")); 45 | System.out.println(lengthOfLongestSubstring2("abcdpwwkew")); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /LeetCode/code/source/maxSubArray.java: -------------------------------------------------------------------------------- 1 | public class maxSubArray { 2 | 3 | public static int maxSubArray(int[] nums) { 4 | int max = Integer.MIN_VALUE; 5 | int sum; 6 | for (int i = 0; i < nums.length; i++) { 7 | sum = 0; 8 | for (int j = i; j < nums.length; j++) { 9 | sum += nums[j]; 10 | if (sum > max) { 11 | max = sum; 12 | } 13 | } 14 | } 15 | return max; 16 | } 17 | 18 | public static void main(String[] args) { 19 | int[] data = new int[]{-2, 1, -3, 4, -1, 2, 1, -5, 4}; 20 | System.out.println(maxSubArray(data)); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /LeetCode/code/source/mergeTwoLists.java: -------------------------------------------------------------------------------- 1 | import struct.ListNode; 2 | 3 | public class mergeTwoLists { 4 | 5 | public ListNode mergeTwoLists(ListNode l1, ListNode l2) { 6 | return new ListNode(4); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /LeetCode/code/source/removeDuplicates.java: -------------------------------------------------------------------------------- 1 | public class removeDuplicates { 2 | 3 | public int removeDuplicates(int[] nums) { 4 | return 1; 5 | } 6 | 7 | } 8 | -------------------------------------------------------------------------------- /LeetCode/code/source/reverse.java: -------------------------------------------------------------------------------- 1 | public class reverse { 2 | 3 | public static int reverse(int x) { 4 | int result = 0; 5 | while (x != 0) { 6 | int pop = x % 10; 7 | if (result > (Integer.MAX_VALUE / 10) || (result == (Integer.MAX_VALUE / 10) && pop > 7)) { 8 | return 0; 9 | } 10 | 11 | if (result < (Integer.MIN_VALUE / 10)|| (result == (Integer.MIN_VALUE / 10) && pop < -8)) { 12 | return 0; 13 | } 14 | result = result * 10 + pop; 15 | x = x / 10; 16 | } 17 | return result; 18 | } 19 | 20 | public static void main(String[] args) { 21 | System.out.println(reverse(Integer.MAX_VALUE-1000-Integer.MIN_VALUE%10+9)); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /LeetCode/code/source/struct/ListNode.java: -------------------------------------------------------------------------------- 1 | package struct; 2 | 3 | public class ListNode { 4 | public int val; 5 | 6 | public ListNode next; 7 | 8 | public ListNode(int x) { 9 | val = x; 10 | } 11 | } -------------------------------------------------------------------------------- /LeetCode/code/source/twoSum.java: -------------------------------------------------------------------------------- 1 | import java.util.HashMap; 2 | import java.util.Map; 3 | 4 | class twoSum { 5 | 6 | public static int[] twoSum(int[] nums, int target) throws Exception { 7 | Map map = new HashMap<>(); 8 | for (int i = 0; i < nums.length; i++) { 9 | map.put(nums[i], i); 10 | } 11 | for (int i = 0; i < nums.length; i++) { 12 | Integer key = target - nums[i]; 13 | if (map.containsKey(key) && i != map.get(key)) { 14 | return new int[]{i, map.get(key)}; 15 | } 16 | } 17 | throw new Exception("no solution"); 18 | } 19 | 20 | public static void main(String[] args) throws Exception { 21 | int nums[] = {1,2, 7, 11, 15}; 22 | int result[] = twoSum(nums, 9); 23 | System.out.println(result[0]+","+result[1]); 24 | } 25 | 26 | } -------------------------------------------------------------------------------- /LeetCode/solution/leetcode/1.两数之和.md: -------------------------------------------------------------------------------- 1 | # 1. 两数之和 2 | 3 | ## 日期 4 | 5 | 2018-11-01 6 | 7 | ## 题目描述 8 | 9 | 给定一个整数数组和一个目标值,找出数组中和为目标值的两个数。 10 | 11 | 你可以假设每个输入只对应一种答案,且同样的元素不能被重复利用。 12 | 13 | 示例: 14 | 15 | ``` 16 | 给定 nums = [2, 7, 11, 15], target = 9 17 | 18 | 因为 nums[0] + nums[1] = 2 + 7 = 9 19 | 所以返回 [0, 1] 20 | ``` 21 | 22 | ## 想法 23 | 24 | 直接使用 HashMap 25 | 26 | ## My 27 | 28 | ``` 29 | class Solution { 30 | public int[] twoSum(int[] nums, int target) { 31 | Map map = new HashMap<>(); 32 | for (int i = 0; i < nums.length; i++) { 33 | map.put(nums[i], i); 34 | } 35 | for (int i = 0; i < nums.length; i++) { 36 | Integer key = target - nums[i]; 37 | if (map.containsKey(key) && i != map.get(key)) { 38 | return new int[]{i, map.get(key)}; 39 | } 40 | } 41 | return null; 42 | } 43 | } 44 | 45 | ``` 46 | 47 | ## Other -------------------------------------------------------------------------------- /LeetCode/solution/leetcode/10.正则表达式匹配.md: -------------------------------------------------------------------------------- 1 | # 10. 正则表达式匹配 2 | 3 | ## 日期 4 | 5 | 2018- 6 | 7 | ## 题目描述 8 | 9 | 给定一个字符串 (s) 和一个字符模式 (p)。实现支持 '.' 和 '*' 的正则表达式匹配。 10 | 11 | ``` 12 | '.' 匹配任意单个字符。 13 | '*' 匹配零个或多个前面的元素。 14 | ``` 15 | 16 | 匹配应该覆盖整个字符串 (s) ,而不是部分字符串。 17 | 18 | 说明: 19 | 20 | - s 可能为空,且只包含从 a-z 的小写字母。 21 | - p 可能为空,且只包含从 a-z 的小写字母,以及字符 . 和 *。 22 | 23 | 示例: 24 | 25 | ``` 26 | 示例 1: 27 | 28 | 输入: 29 | s = "aa" 30 | p = "a" 31 | 输出: false 32 | 解释: "a" 无法匹配 "aa" 整个字符串。 33 | 34 | 示例 2: 35 | 36 | 输入: 37 | s = "aa" 38 | p = "a*" 39 | 输出: true 40 | 解释: '*' 代表可匹配零个或多个前面的元素, 即可以匹配 'a' 。因此, 重复 'a' 一次, 字符串可变为 "aa"。 41 | 42 | 示例 3: 43 | 44 | 输入: 45 | s = "ab" 46 | p = ".*" 47 | 输出: true 48 | 解释: ".*" 表示可匹配零个或多个('*')任意字符('.')。 49 | 50 | 示例 4: 51 | 52 | 输入: 53 | s = "aab" 54 | p = "c*a*b" 55 | 输出: true 56 | 解释: 'c' 可以不被重复, 'a' 可以被重复一次。因此可以匹配字符串 "aab"。 57 | 58 | 示例 5: 59 | 60 | 输入: 61 | s = "mississippi" 62 | p = "mis*is*p*." 63 | 输出: false 64 | ``` 65 | 66 | -------------------------------------------------------------------------------- /LeetCode/solution/leetcode/11.盛最多水的容器.md: -------------------------------------------------------------------------------- 1 | # 盛最多水的容器 2 | 3 | ## 日期 4 | 5 | 2018-11-20 6 | 7 | ## 题目描述 8 | 9 | 给定 n 个非负整数 a1,a2,...,an,每个数代表坐标中的一个点 (i, ai) 。在坐标内画 n 条垂直线,垂直线 i 的两个端点分别为 (i, ai) 和 (i, 0)。找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。 10 | 11 | 说明:你不能倾斜容器,且 n 的值至少为 2。 12 | 13 | ![](http://cdn.heroxu.com/20181119154263741313223.png) 14 | 15 | 16 | 示例: 17 | 18 | ``` 19 | 输入: [1,8,6,2,5,4,8,3,7] 20 | 输出: 49 21 | ``` 22 | 23 | ## 想法 24 | 25 | 双向指针 26 | 27 | ## My 28 | 29 | ``` 30 | public class ContainerWithMostWater { 31 | 32 | public int maxArea(int[] height) { 33 | int max = 0; 34 | int left = 0; 35 | int right = height.length - 1; 36 | while (left < right) { 37 | max = Math.max(max, Math.min(height[left], height[right]) * (right - left)); 38 | if (height[left] < height[right]) { 39 | left++; 40 | } else { 41 | right--; 42 | } 43 | } 44 | return max; 45 | } 46 | } 47 | ``` -------------------------------------------------------------------------------- /LeetCode/solution/leetcode/12.整数转罗马数字.md: -------------------------------------------------------------------------------- 1 | # 整数转罗马数字 2 | 3 | ## 日期 4 | 5 | 2018-11-20 6 | 7 | ## 题目描述 8 | 9 | 罗马数字包含以下七种字符: I, V, X, L,C,D 和 M。 10 | 11 | ``` 12 | 字符 数值 13 | I 1 14 | V 5 15 | X 10 16 | L 50 17 | C 100 18 | D 500 19 | M 1000 20 | ``` 21 | 22 | 例如, 罗马数字 2 写做 II ,即为两个并列的 1。12 写做 XII ,即为 X + II 。 27 写做 XXVII, 即为 XX + V + II 。 23 | 24 | 通常情况下,罗马数字中小的数字在大的数字的右边。但也存在特例,例如 4 不写做 IIII,而是 IV。数字 1 在数字 5 的左边,所表示的数等于大数 5 减小数 1 得到的数值 4 。同样地,数字 9 表示为 IX。这个特殊的规则只适用于以下六种情况: 25 | 26 | - I 可以放在 V (5) 和 X (10) 的左边,来表示 4 和 9。 27 | - X 可以放在 L (50) 和 C (100) 的左边,来表示 40 和 90。 28 | - C 可以放在 D (500) 和 M (1000) 的左边,来表示 400 和 900。 29 | 30 | 给定一个整数,将其转为罗马数字。输入确保在 1 到 3999 的范围内。 31 | 32 | 示例: 33 | 34 | ``` 35 | 示例 1: 36 | 37 | 输入: 3 38 | 输出: "III" 39 | 示例 2: 40 | 41 | 输入: 4 42 | 输出: "IV" 43 | 示例 3: 44 | 45 | 输入: 9 46 | 输出: "IX" 47 | 示例 4: 48 | 49 | 输入: 58 50 | 输出: "LVIII" 51 | 解释: L = 50, V = 5, III = 3. 52 | 示例 5: 53 | 54 | 输入: 1994 55 | 输出: "MCMXCIV" 56 | 解释: M = 1000, CM = 900, XC = 90, IV = 4. 57 | ``` 58 | 59 | ## 想法 60 | 61 | 需要判断 <4 =4 <9 =9 62 | 63 | ## My 64 | 65 | ``` 66 | public class IntegerToRoman { 67 | 68 | public String intToRoman(int num) { 69 | StringBuilder res = new StringBuilder(); 70 | char roman[] = {'M', 'D', 'C', 'L', 'X', 'V', 'I'}; 71 | int value[] = {1000, 500, 100, 50, 10, 5, 1}; 72 | 73 | for (int i = 0; i < 7; i = i + 2) { 74 | int x = num / value[i]; 75 | if (x < 4) { 76 | for (int j = 0; j < x; j++) { 77 | res.append(roman[i]); 78 | } 79 | } else if (x == 4) { 80 | res.append(roman[i]).append(roman[i - 1]); 81 | } else if (x < 9) { 82 | res.append(roman[i - 1]); 83 | for (int j = 5; j < x; j++) { 84 | res.append(roman[i]); 85 | } 86 | } else { 87 | res.append(roman[i]).append(roman[i - 2]); 88 | } 89 | num = num % value[i]; 90 | } 91 | 92 | return res.toString(); 93 | } 94 | } 95 | 96 | ``` 97 | -------------------------------------------------------------------------------- /LeetCode/solution/leetcode/13.罗马数字转整数.md: -------------------------------------------------------------------------------- 1 | # 13. 罗马数字转整数 2 | 3 | ## 日期 4 | 5 | ## 题目描述 6 | 7 | 罗马数字包含以下七种字符: I, V, X, L,C,D 和 M。 8 | 9 | ``` 10 | 字符 数值 11 | I 1 12 | V 5 13 | X 10 14 | L 50 15 | C 100 16 | D 500 17 | M 1000 18 | ``` 19 | 20 | 例如, 罗马数字 2 写做 II ,即为两个并列的 1。12 写做 XII ,即为 X + II 。 27 写做 XXVII, 即为 XX + V + II 。 21 | 22 | 通常情况下,罗马数字中小的数字在大的数字的右边。但也存在特例,例如 4 不写做 IIII,而是 IV。数字 1 在数字 5 的左边,所表示的数等于大数 5 减小数 1 得到的数值 4 。同样地,数字 9 表示为 IX。这个特殊的规则只适用于以下六种情况: 23 | 24 | - I 可以放在 V (5) 和 X (10) 的左边,来表示 4 和 9。 25 | - X 可以放在 L (50) 和 C (100) 的左边,来表示 40 和 90。 26 | - C 可以放在 D (500) 和 M (1000) 的左边,来表示 400 和 900。 27 | 28 | 给定一个罗马数字,将其转换成整数。输入确保在 1 到 3999 的范围内。 29 | 30 | 示例: 31 | 32 | ``` 33 | 示例 1: 34 | 35 | 输入: "III" 36 | 输出: 3 37 | 示例 2: 38 | 39 | 输入: "IV" 40 | 输出: 4 41 | 示例 3: 42 | 43 | 输入: "IX" 44 | 输出: 9 45 | 示例 4: 46 | 47 | 输入: "LVIII" 48 | 输出: 58 49 | 解释: L = 50, V= 5, III = 3. 50 | 示例 5: 51 | 52 | 输入: "MCMXCIV" 53 | 输出: 1994 54 | 解释: M = 1000, CM = 900, XC = 90, IV = 4. 55 | ``` 56 | 57 | ## 想法 58 | 59 | ## My 60 | 61 | ``` 62 | public class RomanToInteger { 63 | 64 | public int romanToInt(String s) { 65 | 66 | Map map = new HashMap<>(); 67 | map.put('I', 1); 68 | map.put('V', 5); 69 | map.put('X', 10); 70 | map.put('L', 50); 71 | map.put('C', 100); 72 | map.put('D', 500); 73 | map.put('M', 1000); 74 | 75 | Integer result = 0; 76 | for (int i = 0; i < s.length(); i++) { 77 | Character c = s.charAt(i); 78 | if (i > 0 && map.get(c) > map.get(s.charAt(i - 1))) { 79 | result = result + map.get(c) - 2 * map.get(s.charAt(i - 1)); 80 | } else { 81 | result = result + map.get(c); 82 | } 83 | } 84 | 85 | return result; 86 | } 87 | } 88 | 89 | ``` -------------------------------------------------------------------------------- /LeetCode/solution/leetcode/14.最长公共前缀.md: -------------------------------------------------------------------------------- 1 | # 14. 最长公共前缀 2 | 3 | ## 日期 4 | 5 | ## 题目描述 6 | 7 | 编写一个函数来查找字符串数组中的最长公共前缀。 8 | 9 | 如果不存在公共前缀,返回空字符串 ""。 10 | 11 | 示例: 12 | 13 | ``` 14 | 示例 1: 15 | 16 | 输入: ["flower","flow","flight"] 17 | 输出: "fl" 18 | 示例 2: 19 | 20 | 输入: ["dog","racecar","car"] 21 | 输出: "" 22 | 解释: 输入不存在公共前缀。 23 | ``` 24 | 25 | 说明: 26 | 27 | 所有输入只包含小写字母 a-z 。 28 | 29 | ## 想法 30 | 31 | ## My -------------------------------------------------------------------------------- /LeetCode/solution/leetcode/15.三数之和.md: -------------------------------------------------------------------------------- 1 | # 15. 三数之和 2 | 3 | ## 日期 4 | 5 | 6 | ## 题目描述 7 | 8 | 给定一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?找出所有满足条件且不重复的三元组。 9 | 10 | 注意:答案中不可以包含重复的三元组。 11 | 12 | 示例: 13 | 14 | ``` 15 | 例如, 给定数组 nums = [-1, 0, 1, 2, -1, -4], 16 | 17 | 满足要求的三元组集合为: 18 | [ 19 | [-1, 0, 1], 20 | [-1, -1, 2] 21 | ] 22 | ``` 23 | 24 | ## 想法 25 | 26 | ## My -------------------------------------------------------------------------------- /LeetCode/solution/leetcode/2.两数相加.md: -------------------------------------------------------------------------------- 1 | # 2. 两数相加 2 | 3 | ## 日期 4 | 5 | 2018-11-09 6 | 7 | ## 题目描述 8 | 9 | 给定两个非空链表来表示两个非负整数。位数按照逆序方式存储,它们的每个节点只存储单个数字。将两数相加返回一个新的链表。 10 | 11 | 你可以假设除了数字 0 之外,这两个数字都不会以零开头。 12 | 13 | 示例: 14 | 15 | ``` 16 | 输入:(2 -> 4 -> 3) + (5 -> 6 -> 4) 17 | 输出:7 -> 0 -> 8 18 | 原因:342 + 465 = 807 19 | ``` 20 | 21 | ## 想法 22 | 23 | 1. 两数相加产生进位(取余为当前节点,取模参加下一个节点计算) 24 | 2. 如果一个链表节点为空,另一个不为空,计算将继续进行(添加节点) 25 | 3. 如果两个节点都为空,但是产生了进位,计算也将继续进行(添加节点) 26 | 27 | ## My 28 | 29 | ``` 30 | class Solution { 31 | 32 | public ListNode addTwoNumbers(ListNode l1, ListNode l2) { 33 | ListNode temp = null; 34 | ListNode l3 = null; 35 | 36 | int mod = 0; 37 | // 只有满足三种情况才退出计算 38 | while (l1 != null || l2 != null || mod != 0) { 39 | int sum = (l1 == null ? 0 : l1.val) + (l2 == null ? 0 : l2.val) + mod; 40 | mod = sum / 10; 41 | //产生 l3 当前节点 42 | ListNode node = new ListNode(sum % 10); 43 | if (temp == null) { 44 | // 产生 head 节点 45 | temp = node; 46 | l3 = temp; 47 | } else { 48 | temp.next = node; 49 | // 移动指针 50 | temp = temp.next; 51 | } 52 | // l1 l2 指向下一个节点 53 | l1 = l1 == null ? null : l1.next; 54 | l2 = l2 == null ? null : l2.next; 55 | } 56 | return l3; 57 | } 58 | } 59 | 60 | ``` 61 | 62 | ## Other -------------------------------------------------------------------------------- /LeetCode/solution/leetcode/20.有效的括号.md: -------------------------------------------------------------------------------- 1 | # 20. 有效的括号 2 | 3 | ## 题目描述 4 | 5 | 给定一个只包括 `'(',')','{','}','[',']'` 的字符串,判断字符串是否有效。 6 | 7 | 有效字符串需满足: 8 | 9 | 左括号必须用相同类型的右括号闭合。 10 | 左括号必须以正确的顺序闭合。 11 | 注意空字符串可被认为是有效字符串。 12 | 13 | ``` 14 | 示例 1: 15 | 16 | 输入: "()" 17 | 输出: true 18 | 示例 2: 19 | 20 | 输入: "()[]{}" 21 | 输出: true 22 | 示例 3: 23 | 24 | 输入: "(]" 25 | 输出: false 26 | 示例 4: 27 | 28 | 输入: "([)]" 29 | 输出: false 30 | 示例 5: 31 | 32 | 输入: "{[]}" 33 | 输出: true 34 | ``` 35 | 36 | 37 | ## 想法 38 | 39 | ## My -------------------------------------------------------------------------------- /LeetCode/solution/leetcode/21.合并两个有序链表.md: -------------------------------------------------------------------------------- 1 | # 21. 合并两个有序链表 2 | 3 | ## 题目描述 4 | 5 | 将两个有序链表合并为一个新的有序链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。 6 | 7 | 示例: 8 | 9 | ``` 10 | 输入:1->2->4, 1->3->4 11 | 输出:1->1->2->3->4->4 12 | ``` -------------------------------------------------------------------------------- /LeetCode/solution/leetcode/26.删除排序数组中的重复项.md: -------------------------------------------------------------------------------- 1 | # 26. 删除排序数组中的重复项 2 | 3 | ## 题目描述 4 | 5 | 给定一个排序数组,你需要在原地删除重复出现的元素,使得每个元素只出现一次,返回移除后数组的新长度。 6 | 7 | 不要使用额外的数组空间,你必须在原地修改输入数组并在使用 O(1) 额外空间的条件下完成。 8 | 9 | 示例: 10 | 11 | ``` 12 | 示例 1: 13 | 14 | 给定数组 nums = [1,1,2], 15 | 16 | 函数应该返回新的长度 2, 并且原数组 nums 的前两个元素被修改为 1, 2。 17 | 18 | 你不需要考虑数组中超出新长度后面的元素。 19 | 20 | 示例 2: 21 | 22 | 给定 nums = [0,0,1,1,1,2,2,3,3,4], 23 | 24 | 函数应该返回新的长度 5, 并且原数组 nums 的前五个元素被修改为 0, 1, 2, 3, 4。 25 | 26 | 你不需要考虑数组中超出新长度后面的元素。 27 | ``` -------------------------------------------------------------------------------- /LeetCode/solution/leetcode/3.无重复字符的最长子串.md: -------------------------------------------------------------------------------- 1 | # 3. 无重复字符的最长子串 2 | 3 | ## 日期 4 | 5 | 2018-11-14 6 | 7 | ## 题目描述 8 | 9 | 给定一个字符串,找出不含有重复字符的最长子串的长度。 10 | 11 | 示例: 12 | 13 | ``` 14 | 示例 1: 15 | 16 | 输入: "abcabcbb" 17 | 输出: 3 18 | 解释: 无重复字符的最长子串是 "abc",其长度为 3。 19 | 示例 2: 20 | 21 | 输入: "bbbbb" 22 | 输出: 1 23 | 解释: 无重复字符的最长子串是 "b",其长度为 1。 24 | 示例 3: 25 | 26 | 输入: "pwwkew" 27 | 输出: 3 28 | 解释: 无重复字符的最长子串是 "wke",其长度为 3。 29 | 请注意,答案必须是一个子串,"pwke" 是一个子序列 而不是子串。 30 | ``` 31 | 32 | ## 想法 33 | 34 | 这是大学里面见过最多的一题了吧,最简单的方法是直接穷尽,这里给出另外两种做法。 35 | 36 | ## My 37 | 38 | - HashMap 39 | 40 | 利用HashMap来定位字符是否出现,如果出现比较两次之间的距离与Max的大小。 41 | 42 | ``` 43 | class Solution { 44 | public int lengthOfLongestSubstring(String s) { 45 | if (s == null || s.length() == 0) { 46 | return 0; 47 | } 48 | 49 | HashMap map = new HashMap<>(); 50 | int max = 0; 51 | int pre = -1; 52 | for (int i = 0, length = s.length(); i < length; i++) { 53 | char ch = s.charAt(i); 54 | Integer chIndex = map.get(ch); 55 | map.put(ch, i); 56 | // 更新重复单词位置 57 | if (chIndex != null) { 58 | pre = Math.max(pre, chIndex); 59 | } 60 | max = Math.max(max, i - pre); 61 | } 62 | 63 | return max; 64 | } 65 | } 66 | 67 | ``` 68 | 69 | 70 | - 缓存表 71 | 72 | 字符串的字符实际上都是有编码的,比如以ASCII编码来看,a=97,所以可以用一个整数数组来保存所有的字符。 73 | 74 | ``` 75 | class Solution { 76 | public int lengthOfLongestSubstring(String s) { 77 | int index[] = new int[128]; 78 | int max = 0; 79 | int pre = 0; 80 | for (int i = 0, length = s.length(); i < length; i++) { 81 | char ch = s.charAt(i); 82 | Integer chIndex = index[ch]; 83 | pre = Math.max(pre, chIndex); 84 | max = Math.max(max, i - pre + 1); 85 | index[ch] = i + 1; 86 | } 87 | 88 | return max; 89 | } 90 | } 91 | 92 | ``` 93 | 94 | ## Other -------------------------------------------------------------------------------- /LeetCode/solution/leetcode/4.两个排序数组的中位数.md: -------------------------------------------------------------------------------- 1 | # 4. 两个排序数组的中位数 2 | 3 | ## 日期 4 | 5 | ## 题目描述 6 | 7 | 给定两个大小为 m 和 n 的有序数组 nums1 和 nums2 。 8 | 9 | 请找出这两个有序数组的中位数。要求算法的时间复杂度为 O(log (m+n)) 。 10 | 11 | 你可以假设 nums1 和 nums2 不同时为空。 12 | 13 | 示例: 14 | 15 | ``` 16 | 示例 1: 17 | 18 | nums1 = [1, 3] 19 | nums2 = [2] 20 | 21 | 中位数是 2.0 22 | 示例 2: 23 | 24 | nums1 = [1, 2] 25 | nums2 = [3, 4] 26 | 27 | 中位数是 (2 + 3)/2 = 2.5 28 | ``` 29 | 30 | ## 想法 31 | 32 | 如果排序结果为奇数,中位数是中间的那个数,如果是偶数,中位数是中间两个数的 1/2 -------------------------------------------------------------------------------- /LeetCode/solution/leetcode/5.最长回文子串.md: -------------------------------------------------------------------------------- 1 | # 5. 最长回文子串 2 | 3 | ## 日期 4 | 5 | ## 题目描述 6 | 7 | 给定一个字符串 s,找到 s 中最长的回文子串。你可以假设 s 的最大长度为1000。 8 | 9 | 示例: 10 | 11 | ``` 12 | 示例 1: 13 | 14 | 输入: "babad" 15 | 输出: "bab" 16 | 注意: "aba"也是一个有效答案。 17 | 示例 2: 18 | 19 | 输入: "cbbd" 20 | 输出: "bb" 21 | ``` 22 | 23 | ## 想法 24 | 25 | 经典题之一 26 | 27 | -------------------------------------------------------------------------------- /LeetCode/solution/leetcode/53.最大子序和.md: -------------------------------------------------------------------------------- 1 | # 53. 最大子序和 2 | 3 | ## 日期 4 | 5 | 2018-11-01 6 | 7 | ## 题目描述 8 | 9 | 给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。 10 | 11 | 示例: 12 | 13 | ``` 14 | 输入: [-2,1,-3,4,-1,2,1,-5,4], 15 | 输出: 6 16 | 解释: 连续子数组 [4,-1,2,1] 的和最大,为 6。 17 | ``` 18 | 19 | 进阶: 20 | 21 | 如果你已经实现复杂度为 O(n) 的解法,尝试使用更为精妙的分治法求解。 22 | 23 | ## 思路 24 | 25 | 暴力计算出所有连续加值。 26 | 27 | ## My 28 | 29 | ``` 30 | public static int maxSubArray(int[] nums) { 31 | int max = Integer.MIN_VALUE; 32 | int sum; 33 | for (int i = 0; i < nums.length; i++) { 34 | sum = 0; 35 | for (int j = i; j < nums.length; j++) { 36 | sum += nums[j]; 37 | if (sum > max) { 38 | max = sum; 39 | } 40 | } 41 | } 42 | return max; 43 | } 44 | ``` 45 | -------------------------------------------------------------------------------- /LeetCode/solution/leetcode/6.Z字型变化.md: -------------------------------------------------------------------------------- 1 | # Z字型变化 2 | 3 | ## 日期 4 | 5 | 2018-11-15 6 | 7 | ## 题目描述 8 | 9 | 将字符串 "PAYPALISHIRING" 以Z字形排列成给定的行数: 10 | 11 | ``` 12 | P A H N 13 | A P L S I I G 14 | Y I R 15 | ``` 16 | 17 | 之后从左往右,逐行读取字符:"PAHNAPLSIIGYIR" 18 | 19 | 实现一个将字符串进行指定行数变换的函数: 20 | 21 | string convert(string s, int numRows); 22 | 23 | 示例: 24 | 25 | ``` 26 | 示例 1: 27 | 28 | 输入: s = "PAYPALISHIRING", numRows = 3 29 | 输出: "PAHNAPLSIIGYIR" 30 | 示例 2: 31 | 32 | 输入: s = "PAYPALISHIRING", numRows = 4 33 | 输出: "PINALSIGYAHRPI" 34 | 解释: 35 | 36 | P I N 37 | A L S I G 38 | Y A H R 39 | P I 40 | ``` 41 | 42 | ## 想法 43 | 44 | ## My 45 | 46 | ``` 47 | class Solution { 48 | public String convert(String s, int numRows) { 49 | int length = s.length(); 50 | int nodeLength = numRows * 2 - 2; 51 | 52 | if (length == 0 | numRows == 0 | numRows == 1) { 53 | return s; 54 | } 55 | String result = ""; 56 | for (int i = 0; i < numRows; i++) { 57 | for (int j = i; j < length; j += nodeLength) { 58 | // 整列数据直接等差 59 | result += s.charAt(j); 60 | int single = j - 2 * i + nodeLength; 61 | // 去除首行,尾行,且不能超过最长长度 62 | if (i != 0 && i != numRows - 1 && single < length) { 63 | result += s.charAt(single); 64 | } 65 | } 66 | } 67 | 68 | return result; 69 | } 70 | } 71 | ``` -------------------------------------------------------------------------------- /LeetCode/solution/leetcode/7.反转整数.md: -------------------------------------------------------------------------------- 1 | # 7. 反转整数 2 | 3 | ## 日期 4 | 5 | 2018-11-16 6 | 7 | ## 题目描述 8 | 9 | 给定一个 32 位有符号整数,将整数中的数字进行反转。 10 | 11 | 示例: 12 | 13 | ``` 14 | 示例 1: 15 | 16 | 输入: 123 17 | 输出: 321 18 | 示例 2: 19 | 20 | 输入: -123 21 | 输出: -321 22 | 示例 3: 23 | 24 | 输入: 120 25 | 输出: 21 26 | 注意: 27 | 28 | 假设我们的环境只能存储 32 位有符号整数,其数值范围是 [−2^31, 2^31 − 1]。根据这个假设,如果反转后的整数溢出,则返回 0。 29 | ``` 30 | 31 | ## 想法 32 | 33 | 需要注意溢出问题,2^32%10=6 34 | 35 | ## My 36 | 37 | ``` 38 | class Solution { 39 | public int reverse(int x) { 40 | int result = 0; 41 | while (x != 0) { 42 | int pop = x % 10; 43 | if (result > (Integer.MAX_VALUE / 10) || (result == (Integer.MAX_VALUE / 10) && pop > 7)) { 44 | return 0; 45 | } 46 | 47 | if (result < (Integer.MIN_VALUE / 10)|| (result == (Integer.MIN_VALUE / 10) && pop < -8)) { 48 | return 0; 49 | } 50 | result = result * 10 + pop; 51 | x = x / 10; 52 | } 53 | return result; 54 | } 55 | } 56 | ``` -------------------------------------------------------------------------------- /LeetCode/solution/leetcode/8.字符串转整数(atoi).md: -------------------------------------------------------------------------------- 1 | # 8. 字符串转整数(atoi) 2 | 3 | ## 日期 4 | 5 | 2018-11-17 6 | 7 | ## 题目描述 8 | 9 | 实现 atoi,将字符串转为整数。 10 | 11 | 该函数首先根据需要丢弃任意多的空格字符,直到找到第一个非空格字符为止。如果第一个非空字符是正号或负号,选取该符号,并将其与后面尽可能多的连续的数字组合起来,这部分字符即为整数的值。如果第一个非空字符是数字,则直接将其与之后连续的数字字符组合起来,形成整数。 12 | 13 | 字符串可以在形成整数的字符后面包括多余的字符,这些字符可以被忽略,它们对于函数没有影响。 14 | 15 | 当字符串中的第一个非空字符序列不是个有效的整数;或字符串为空;或字符串仅包含空白字符时,则不进行转换。 16 | 17 | 若函数不能执行有效的转换,返回 0。 18 | 19 | 说明: 20 | 21 | 假设我们的环境只能存储 32 位有符号整数,其数值范围是 [−2^31, 2^31 − 1]。如果数值超过可表示的范围,则返回 INT_MAX (231 − 1) 或 INT_MIN (−231) 。 22 | 23 | 示例: 24 | 25 | ``` 26 | 示例 1: 27 | 28 | 输入: "42" 29 | 输出: 42 30 | 示例 2: 31 | 32 | 输入: " -42" 33 | 输出: -42 34 | 解释: 第一个非空白字符为 '-', 它是一个负号。 35 | 我们尽可能将负号与后面所有连续出现的数字组合起来,最后得到 -42 。 36 | 示例 3: 37 | 38 | 输入: "4193 with words" 39 | 输出: 4193 40 | 解释: 转换截止于数字 '3' ,因为它的下一个字符不为数字。 41 | 示例 4: 42 | 43 | 输入: "words and 987" 44 | 输出: 0 45 | 解释: 第一个非空字符是 'w', 但它不是数字或正、负号。 46 | 因此无法执行有效的转换。 47 | 示例 5: 48 | 49 | 输入: "-91283472332" 50 | 输出: -2147483648 51 | 解释: 数字 "-91283472332" 超过 32 位有符号整数范围。 52 | 因此返回 INT_MIN (−231) 。 53 | ``` 54 | 55 | ## 56 | 57 | ## My 58 | 59 | ``` 60 | public static int myAtoi(String str) { 61 | 62 | if (str.isEmpty()) { 63 | return 0; 64 | } 65 | 66 | int length = str.length(); 67 | int sign = 1; 68 | int result = 0; 69 | int i = 0; 70 | 71 | while (i < length && str.charAt(i) == ' ') { 72 | i++; 73 | } 74 | 75 | if (i < length && (str.charAt(i) == '-'|| str.charAt(i) == '+')) { 76 | sign = str.charAt(i++) == '-' ? -1 : 1; 77 | } 78 | 79 | while (i < length && str.charAt(i) >= '0' && str.charAt(i) <= '9') { 80 | if (result > Integer.MAX_VALUE / 10 || result == Integer.MAX_VALUE / 10 && str.charAt(i) - '0' > 7) { 81 | return sign == 1 ? Integer.MAX_VALUE : Integer.MIN_VALUE; 82 | } 83 | result = result * 10 + (str.charAt(i++) - '0'); 84 | } 85 | 86 | return result * sign; 87 | } 88 | ``` -------------------------------------------------------------------------------- /LeetCode/solution/leetcode/9.回文数.md: -------------------------------------------------------------------------------- 1 | # 9. 回文数 2 | 3 | ## 日期 4 | 5 | 2018-11-19 6 | 7 | ## 题目描述 8 | 9 | 判断一个整数是否是回文数。回文数是指正序(从左向右)和倒序(从右向左)读都是一样的整数。 10 | 11 | 示例: 12 | 13 | ``` 14 | 示例 1: 15 | 16 | 输入: 121 17 | 输出: true 18 | 19 | 示例 2: 20 | 21 | 输入: -121 22 | 输出: false 23 | 解释: 从左向右读, 为 -121 。 从右向左读, 为 121- 。因此它不是一个回文数。 24 | 25 | 示例 3: 26 | 27 | 输入: 10 28 | 输出: false 29 | 解释: 从右向左读, 为 01 。因此它不是一个回文数。 30 | ``` 31 | 32 | 进阶: 33 | 34 | 你能不将整数转为字符串来解决这个问题吗? 35 | 36 | ## 想法 37 | 38 | 1. 反转整数,比较 39 | 2. 直接把数字拆成两半比较 40 | 41 | ## My 42 | 43 | 解法2: 44 | 45 | ``` 46 | public class PalindromeNumber { 47 | 48 | public boolean isPalindrome(int x) { 49 | if (x < 0 || x != 0 && x % 10 == 0) { 50 | return false; 51 | } 52 | int num = 0; 53 | while (x > num) { 54 | num = num * 10 + x % 10; 55 | x = x / 10; 56 | } 57 | return x == num || num / 10 == x; 58 | } 59 | } 60 | ``` -------------------------------------------------------------------------------- /LeetCode/排序算法/1356_根据数字二进制下 1 的数目排序.md: -------------------------------------------------------------------------------- 1 | ## 题目描述 2 | 3 | 给你一个整数数组 arr 。请你将数组中的元素按照其二进制表示中数字 1 的数目升序排序。 4 | 5 | 如果存在多个数字二进制中 1 的数目相同,则必须将它们按照数值大小升序排列。 6 | 7 | 请你返回排序后的数组。 8 | 9 | **示例 1:** 10 | 11 | ``` 12 | 输入:arr = [0,1,2,3,4,5,6,7,8] 13 | 输出:[0,1,2,4,8,3,5,6,7] 14 | 解释:[0] 是唯一一个有 0 个 1 的数。 15 | [1,2,4,8] 都有 1 个 1 。 16 | [3,5,6] 有 2 个 1 。 17 | [7] 有 3 个 1 。 18 | 按照 1 的个数排序得到的结果数组为 [0,1,2,4,8,3,5,6,7] 19 | ``` 20 | 21 | **示例 2:** 22 | 23 | ``` 24 | 输入:arr = [1024,512,256,128,64,32,16,8,4,2,1] 25 | 输出:[1,2,4,8,16,32,64,128,256,512,1024] 26 | 解释:数组中所有整数二进制下都只有 1 个 1 ,所以你需要按照数值大小将它们排序。 27 | ``` 28 | 29 | **示例 3:** 30 | 31 | ``` 32 | 输入:arr = [10000,10000] 33 | 输出:[10000,10000] 34 | ``` 35 | 36 | **示例 4:** 37 | 38 | ``` 39 | 输入:arr = [2,3,5,7,11,13,17,19] 40 | 输出:[2,3,5,17,7,11,13,19] 41 | ``` 42 | 43 | **示例 5:** 44 | 45 | ``` 46 | 输入:arr = [10,100,1000,10000] 47 | 输出:[10,100,10000,1000] 48 | ``` 49 | 50 | **提示:** 51 | 52 | - 1 <= arr.length <= 500 53 | - 0 <= arr[i] <= 10^4 54 | 55 | ## 方法一:暴力 56 | 57 | 对每个十进制的数转二进制的时候统计二进制表示中的 1 的个数即可。 58 | 59 | ## 方法二:递推预处理 60 | 我们定义 bit[i] 为数字 i 二进制表示下数字 1 的个数,则可以列出递推式: 61 | 62 | bit[i]=bit[i>>1]+(i&1) 63 | 64 | 所以我们线性预处理 bitbit 数组然后去排序即可。 65 | 66 | ``` 67 | class Solution { 68 | public int[] sortByBits(int[] arr) { 69 | List list = new ArrayList(); 70 | for (int x : arr) { 71 | list.add(x); 72 | } 73 | int[] bit = new int[10001]; 74 | for (int i = 1; i <= 10000; ++i) { 75 | bit[i] = bit[i >> 1] + (i & 1); 76 | } 77 | Collections.sort(list, new Comparator() { 78 | public int compare(Integer x, Integer y) { 79 | if (bit[x] != bit[y]) { 80 | return bit[x] - bit[y]; 81 | } else { 82 | return x - y; 83 | } 84 | } 85 | }); 86 | for (int i = 0; i < arr.length; ++i) { 87 | arr[i] = list.get(i); 88 | } 89 | return arr; 90 | } 91 | } 92 | ``` 93 | 94 | ### 复杂度分析 95 | 96 | - 时间复杂度:O(nlogn),其中 n 为整数数组 arr 的长度。 97 | 98 | - 空间复杂度:O(n),其中 n 为整数数组 arr 的长度。 99 | 100 | -------------------------------------------------------------------------------- /LeetCode/排序算法/1370_上升下降字符串.md: -------------------------------------------------------------------------------- 1 | ## 题目描述 2 | 3 | 给你一个字符串 s ,请你根据下面的算法重新构造字符串: 4 | 5 | 1. 从 s 中选出 最小 的字符,将它 接在 结果字符串的后面。 6 | 2. 从 s 剩余字符中选出 最小 的字符,且该字符比上一个添加的字符大,将它 接在 结果字符串后面。 7 | 3. 重复步骤 2 ,直到你没法从 s 中选择字符。 8 | 4. 从 s 中选出 最大 的字符,将它 接在 结果字符串的后面。 9 | 5. 从 s 剩余字符中选出 最大 的字符,且该字符比上一个添加的字符小,将它 接在 结果字符串后面。 10 | 6. 重复步骤 5 ,直到你没法从 s 中选择字符。 11 | 7. 重复步骤 1 到 6 ,直到 s 中所有字符都已经被选过。 12 | 8. 在任何一步中,如果最小或者最大字符不止一个 ,你可以选择其中任意一个,并将其添加到结果字符串。 13 | 14 | 请你返回将 s 中字符重新排序后的 结果字符串 。 15 | 16 | 示例 1: 17 | ``` 18 | 输入:s = "aaaabbbbcccc" 19 | 输出:"abccbaabccba" 20 | 解释:第一轮的步骤 1,2,3 后,结果字符串为 result = "abc" 21 | 第一轮的步骤 4,5,6 后,结果字符串为 result = "abccba" 22 | 第一轮结束,现在 s = "aabbcc" ,我们再次回到步骤 1 23 | 第二轮的步骤 1,2,3 后,结果字符串为 result = "abccbaabc" 24 | 第二轮的步骤 4,5,6 后,结果字符串为 result = "abccbaabccba" 25 | ``` 26 | 27 | 示例 2: 28 | 29 | ``` 30 | 输入:s = "rat" 31 | 输出:"art" 32 | 解释:单词 "rat" 在上述算法重排序以后变成 "art" 33 | ``` 34 | 35 | 示例 3: 36 | 37 | ``` 38 | 输入:s = "leetcode" 39 | 输出:"cdelotee" 40 | ``` 41 | 42 | 示例 4: 43 | 44 | ``` 45 | 输入:s = "ggggggg" 46 | 输出:"ggggggg" 47 | ``` 48 | 49 | 示例 5: 50 | 51 | ``` 52 | 输入:s = "spo" 53 | 输出:"ops" 54 | ``` 55 | 56 | 提示: 57 | 58 | - 1 <= s.length <= 500 59 | - s 只包含小写英文字母。 60 | 61 | ## 解法 62 | 63 | ### 方法一:桶计数 64 | 65 | **思路及解法** 66 | 67 | 仔细分析步骤,我们发现: 68 | 69 | - 每个字符被选择且仅被选择一次; 70 | 71 | - 每一轮会在字符串末尾加入一个先升后降的字符串,且该串的上升部分和下降部分都会尽可能长。 72 | 73 | 于是我们重复若干轮下述操作,直到每一个字符都被选择过,这样就可以构造出这个字符串: 74 | 75 | - 先从未被选择的字符中提取出最长的上升字符串,将其加入答案。 76 | 77 | - 然后从未被选择的字符中提取出最长的下降字符串,将其加入答案。 78 | 79 | 注意到在构造时我们只关注字符本身,而不关注字符在原字符串中的位置。因此我们可以直接创建一个大小为 `26` 的桶,记录每种字符的数量。每次提取最长的上升或下降字符串时,我们直接顺序或逆序遍历这个桶。 80 | 81 | 具体地,在遍历桶的过程中,如果当前桶的计数值不为零,那么将当前桶对应的字符加入到答案中,并将当前桶的计数值减一即可。我们重复这一过程,直到答案字符串的长度与传入的字符串的长度相等。 82 | 83 | ``` 84 | class Solution { 85 | public String sortString(String s) { 86 | int[] num = new int[26]; 87 | for (int i = 0; i < s.length(); i++) { 88 | num[s.charAt(i) - 'a']++; 89 | } 90 | 91 | StringBuffer ret = new StringBuffer(); 92 | while (ret.length() < s.length()) { 93 | for (int i = 0; i < 26; i++) { 94 | if (num[i] > 0) { 95 | ret.append((char) (i + 'a')); 96 | num[i]--; 97 | } 98 | } 99 | for (int i = 25; i >= 0; i--) { 100 | if (num[i] > 0) { 101 | ret.append((char) (i + 'a')); 102 | num[i]--; 103 | } 104 | } 105 | } 106 | return ret.toString(); 107 | } 108 | } 109 | ``` 110 | 111 | ## 刷题记录 112 | 113 | - 2021-04-07 16:20 114 | - 2021-04-08 09:31 115 | -------------------------------------------------------------------------------- /LeetCode/排序算法/1502_判断能否形成等差数列.md: -------------------------------------------------------------------------------- 1 | ## 题目描述 2 | 3 | 给你一个数字数组 arr 。 4 | 5 | 如果一个数列中,任意相邻两项的差总等于同一个常数,那么这个数列就称为 等差数列 。 6 | 7 | 如果可以重新排列数组形成等差数列,请返回 true ;否则,返回 false 。 8 | 9 | 10 | **示例 1:** 11 | 12 | ``` 13 | 输入:arr = [3,5,1] 14 | 输出:true 15 | 解释:对数组重新排序得到 [1,3,5] 或者 [5,3,1] ,任意相邻两项的差分别为 2 或 -2 ,可以形成等差数列。 16 | ``` 17 | 18 | **示例 2:** 19 | 20 | ``` 21 | 输入:arr = [1,2,4] 22 | 输出:false 23 | 解释:无法通过重新排序得到等差数列。 24 | ``` 25 | 26 | **提示:** 27 | 28 | - 2 <= arr.length <= 1000 29 | - -10^6 <= arr[i] <= 10^6 30 | 31 | ## 方法一:模拟 32 | 33 | 首先我们对原序列排序,假设排序之后序列为{a0,a1,⋯an},如果对 i∈[1,n−1] 中的每个数都有 a_i x 2 = a_{i - 1} + a_{i + 1}成立,那么这个数列就是等差数列。 34 | 35 | ``` 36 | class Solution { 37 | public boolean canMakeArithmeticProgression(int[] arr) { 38 | Arrays.sort(arr); 39 | for (int i = 1; i < arr.length - 1; ++i) { 40 | if (arr[i] * 2 != arr[i - 1] + arr[i + 1]) { 41 | return false; 42 | } 43 | } 44 | return true; 45 | } 46 | } 47 | ``` 48 | 49 | ### 复杂度分析 50 | 51 | - 时间复杂度:O(nlogn)。排序的时间代价为 O(nlogn),遍历序列的时间代价是 O(n),故渐进时间复杂度为 O(nlogn+n)=O(nlogn)。 52 | - 空间复杂度:O(logn)。快速排序中使用的栈空间期望是 O(logn)。 53 | 54 | -------------------------------------------------------------------------------- /LeetCode/排序算法/1528_重新排列字符串.md: -------------------------------------------------------------------------------- 1 | ## 题目描述 2 | 3 | 给你一个字符串 s 和一个 长度相同 的整数数组 indices 。 4 | 5 | 请你重新排列字符串 s ,其中第 i 个字符需要移动到 indices[i] 指示的位置。 6 | 7 | 返回重新排列后的字符串。 8 | 9 | **示例 1:** 10 | 11 | ![20210407162129](http://cdn.heroxu.com/20210407162129.png) 12 | 13 | ``` 14 | 输入:s = "codeleet", indices = [4,5,6,7,0,2,1,3] 15 | 输出:"leetcode" 16 | 解释:如图所示,"codeleet" 重新排列后变为 "leetcode" 。 17 | ``` 18 | 19 | **示例 2:** 20 | 21 | ``` 22 | 输入:s = "abc", indices = [0,1,2] 23 | 输出:"abc" 24 | 解释:重新排列后,每个字符都还留在原来的位置上。 25 | ``` 26 | 27 | **示例 3:** 28 | 29 | ``` 30 | 输入:s = "aiohn", indices = [3,1,4,2,0] 31 | 输出:"nihao" 32 | ``` 33 | 34 | **示例 4:** 35 | 36 | ``` 37 | 输入:s = "aaiougrt", indices = [4,0,2,6,7,3,1,5] 38 | 输出:"arigatou" 39 | ``` 40 | 41 | **示例 5:** 42 | 43 | ``` 44 | 输入:s = "art", indices = [1,0,2] 45 | 输出:"rat" 46 | ``` 47 | 48 | **提示:** 49 | 50 | - s.length == indices.length == n 51 | - 1 <= n <= 100 52 | - s 仅包含小写英文字母。 53 | - 0 <= indices[i] < n 54 | - indices 的所有的值都是唯一的(也就是说,indices 是整数 0 到 n - 1 形成的一组排列)。 55 | 56 | 57 | ## 方法一:模拟 58 | 59 | ### 思路与算法 60 | 61 | 创建一个新字符串 `result` 来存储答案。对于 `s` 每个下标 `i`,将 `result[indices[i]]` 处的字符设成 `s[i]` 即可。 62 | 63 | ### 代码 64 | 65 | ``` 66 | class Solution { 67 | public String restoreString(String s, int[] indices) { 68 | int length = s.length(); 69 | char[] result = new char[length]; 70 | 71 | for (int i = 0; i < length; i++) { 72 | result[indices[i]] = s.charAt(i); 73 | } 74 | return new String(result); 75 | } 76 | } 77 | ``` 78 | 79 | ### 复杂度分析 80 | 81 | - 时间复杂度:O(N),其中 N 为字符串 s 的长度。我们只需对字符串 s 执行一次线性扫描即可。 82 | 83 | - 空间复杂度:O(1) 或 O(N)。除开辟的存储答案的字符串外,我们只需要常数空间存放若干变量。如果使用的语言不允许对字符串进行修改,我们还需要 O(N) 的空间临时存储答案。 84 | 85 | ## 刷题记录 86 | 87 | - 2021-04-08 11:11 88 | 89 | -------------------------------------------------------------------------------- /LeetCode/排序算法/349_两个数组的交集.md: -------------------------------------------------------------------------------- 1 | ## 题目描述 2 | 3 | 给定两个数组,编写一个函数来计算它们的交集。 4 | 5 | **示例 1:** 6 | 7 | ``` 8 | 输入:nums1 = [1,2,2,1], nums2 = [2,2] 9 | 输出:[2] 10 | ``` 11 | 12 | **示例 2:** 13 | 14 | ``` 15 | 输入:nums1 = [4,9,5], nums2 = [9,4,9,8,4] 16 | 输出:[9,4] 17 | ``` 18 | 19 | **说明:** 20 | 21 | - 输出结果中的每个元素一定是唯一的。 22 | - 我们可以不考虑输出结果的顺序。 23 | 24 | ## 方法一:两个集合 25 | 计算两个数组的交集,直观的方法是遍历数组 nums1,对于其中的每个元素,遍历数组 nums2 判断该元素是否在数组 nums2 中,如果存在,则将该元素添加到返回值。假设数组 nums1 和 nums2 的长度分别是 m 和 n,则遍历数组 nums1 需要 O(m) 的时间,判断 nums1 中的每个元素是否在数组 nums2 中需要 O(n) 的时间,因此总时间复杂度是 O(mn)。 26 | 27 | 如果使用哈希集合存储元素,则可以在 O(1) 的时间内判断一个元素是否在集合中,从而降低时间复杂度。 28 | 29 | 首先使用两个集合分别存储两个数组中的元素,然后遍历较小的集合,判断其中的每个元素是否在另一个集合中,如果元素也在另一个集合中,则将该元素添加到返回值。该方法的时间复杂度可以降低到 O(m+n)。 30 | 31 | ``` 32 | class Solution { 33 | public int[] intersection(int[] nums1, int[] nums2) { 34 | Set set1 = new HashSet(); 35 | Set set2 = new HashSet(); 36 | for (int num : nums1) { 37 | set1.add(num); 38 | } 39 | for (int num : nums2) { 40 | set2.add(num); 41 | } 42 | return getIntersection(set1, set2); 43 | } 44 | 45 | public int[] getIntersection(Set set1, Set set2) { 46 | if (set1.size() > set2.size()) { 47 | return getIntersection(set2, set1); 48 | } 49 | Set intersectionSet = new HashSet(); 50 | for (int num : set1) { 51 | if (set2.contains(num)) { 52 | intersectionSet.add(num); 53 | } 54 | } 55 | int[] intersection = new int[intersectionSet.size()]; 56 | int index = 0; 57 | for (int num : intersectionSet) { 58 | intersection[index++] = num; 59 | } 60 | return intersection; 61 | } 62 | } 63 | ``` 64 | 65 | ### 复杂度分析 66 | 67 | - 时间复杂度:O(m+n),其中 m 和 n 分别是两个数组的长度。使用两个集合分别存储两个数组中的元素需要 O(m+n) 的时间,遍历较小的集合并判断元素是否在另一个集合中需要 O(min(m,n)) 的时间,因此总时间复杂度是 O(m+n)。 68 | 69 | - 空间复杂度:O(m+n),其中 m 和 n 分别是两个数组的长度。空间复杂度主要取决于两个集合。 70 | 71 | ## 方法二:排序 + 双指针 72 | 73 | 如果两个数组是有序的,则可以使用双指针的方法得到两个数组的交集。 74 | 75 | 首先对两个数组进行排序,然后使用两个指针遍历两个数组。可以预见的是加入答案的数组的元素一定是递增的,为了保证加入元素的唯一性,我们需要额外记录变量 pre 表示上一次加入答案数组的元素。 76 | 77 | 初始时,两个指针分别指向两个数组的头部。每次比较两个指针指向的两个数组中的数字,如果两个数字不相等,则将指向较小数字的指针右移一位,如果两个数字相等,且该数字不等于 pre ,将该数字添加到答案并更新 pre 变量,同时将两个指针都右移一位。当至少有一个指针超出数组范围时,遍历结束。 78 | 79 | ``` 80 | class Solution { 81 | public int[] intersection(int[] nums1, int[] nums2) { 82 | Arrays.sort(nums1); 83 | Arrays.sort(nums2); 84 | int index=0; int index1=0; int index2=0; 85 | int length1=nums1.length; 86 | int length2=nums2.length; 87 | int[] ret=new int[length1+length2]; 88 | while(index1 nums2.length) { 35 | return intersect(nums2, nums1); 36 | } 37 | Map map = new HashMap(); 38 | for (int num : nums1) { 39 | int count = map.getOrDefault(num, 0) + 1; 40 | map.put(num, count); 41 | } 42 | int[] intersection = new int[nums1.length]; 43 | int index = 0; 44 | for (int num : nums2) { 45 | int count = map.getOrDefault(num, 0); 46 | if (count > 0) { 47 | intersection[index++] = num; 48 | count--; 49 | if (count > 0) { 50 | map.put(num, count); 51 | } else { 52 | map.remove(num); 53 | } 54 | } 55 | } 56 | return Arrays.copyOfRange(intersection, 0, index); 57 | } 58 | } 59 | ``` 60 | 61 | ## 方法二:排序 + 双指针 62 | 63 | 如果两个数组是有序的,则可以使用双指针的方法得到两个数组的交集。 64 | 65 | 首先对两个数组进行排序,然后使用两个指针遍历两个数组。 66 | 67 | 初始时,两个指针分别指向两个数组的头部。每次比较两个指针指向的两个数组中的数字,如果两个数字不相等,则将指向较小数字的指针右移一位,如果两个数字相等,将该数字添加到答案,并将两个指针都右移一位。当至少有一个指针超出数组范围时,遍历结束。 68 | 69 | ``` 70 | class Solution { 71 | public int[] intersect(int[] nums1, int[] nums2) { 72 | Arrays.sort(nums1); 73 | Arrays.sort(nums2); 74 | int index=0; int index1=0; int index2=0; 75 | int length1=nums1.length; 76 | int length2=nums2.length; 77 | int[] ret=new int[length1+length2]; 78 | while(index1 maxValue) { 42 | maxValue = value; 43 | } 44 | } 45 | 46 | int bucketCount = (int) Math.floor((maxValue - minValue) / bucketSize) + 1; 47 | int[][] buckets = new int[bucketCount][0]; 48 | 49 | // 利用映射函数将数据分配到各个桶中 50 | for (int i = 0; i < arr.length; i++) { 51 | int index = (int) Math.floor((arr[i] - minValue) / bucketSize); 52 | buckets[index] = arrAppend(buckets[index], arr[i]); 53 | } 54 | 55 | int arrIndex = 0; 56 | for (int[] bucket : buckets) { 57 | if (bucket.length <= 0) { 58 | continue; 59 | } 60 | // 对每个桶进行排序,这里使用了插入排序 61 | bucket = insertSort.sort(bucket); 62 | for (int value : bucket) { 63 | arr[arrIndex++] = value; 64 | } 65 | } 66 | 67 | return arr; 68 | } 69 | 70 | /** 71 | * 自动扩容,并保存数据 72 | * 73 | * @param arr 74 | * @param value 75 | */ 76 | private int[] arrAppend(int[] arr, int value) { 77 | arr = Arrays.copyOf(arr, arr.length + 1); 78 | arr[arr.length - 1] = value; 79 | return arr; 80 | } 81 | 82 | } 83 | ``` 84 | 85 | ## 参考 86 | 87 | - [计数排序,基数排序和桶排序](https://www.cnblogs.com/ttltry-air/archive/2012/08/04/2623302.html) -------------------------------------------------------------------------------- /LeetCode/数据结构/二叉树/2236-判断根结点是否等于子结点之和.md: -------------------------------------------------------------------------------- 1 | # 判断根结点是否等于子结点之和 2 | 3 | ## 题目描述 4 | 5 | 给你一个 二叉树 的根结点 root,该二叉树由恰好 3 个结点组成:根结点、左子结点和右子结点。 6 | 7 | 如果根结点值等于两个子结点值之和,返回 true ,否则返回 false 。 8 | 9 | **示例 1:** 10 | 11 | ![VopGg41nFxtDqPH.png](https://s2.loli.net/2022/07/04/VopGg41nFxtDqPH.png) 12 | 13 | ![28h4U5HfpK3omGL.png](https://s2.loli.net/2022/07/04/28h4U5HfpK3omGL.png) 14 | ``` 15 | 输入:root = [10,4,6] 16 | 输出:true 17 | 解释:根结点、左子结点和右子结点的值分别是 10 、4 和 6 。 18 | 由于 10 等于 4 + 6 ,因此返回 true 。 19 | ``` 20 | 21 | **示例 2:** 22 | 23 | ![p7KNnXZV3FsH1v8.png](https://s2.loli.net/2022/07/04/p7KNnXZV3FsH1v8.png) 24 | 25 | ![3BeYGpqLPEMDHXu.png](https://s2.loli.net/2022/07/04/3BeYGpqLPEMDHXu.png) -------------------------------------------------------------------------------- /LeetCode/数据结构/二叉树/993-二叉树的堂兄弟节点.md: -------------------------------------------------------------------------------- 1 | # 二叉树的堂兄弟节点 2 | 3 | ## 题目描述 4 | 5 | 在二叉树中,根节点位于深度 0 处,每个深度为 k 的节点的子节点位于深度 k+1 处。 6 | 7 | 如果二叉树的两个节点深度相同,但 父节点不同 ,则它们是一对堂兄弟节点。 8 | 9 | 我们给出了具有唯一值的二叉树的根节点 root ,以及树中两个不同节点的值 x 和 y 。 10 | 11 | 只有与值 x 和 y 对应的节点是堂兄弟节点时,才返回 true 。否则,返回 false。 12 | 13 |   14 | 15 | 示例 1: 16 | 17 | ![7B978AA0A4138BAC](http://cdn.heroxu.com/7B978AA0A4138BAC.png) 18 | 19 | ``` 20 | 输入:root = [1,2,3,4], x = 4, y = 3 21 | 输出:false 22 | ``` 23 | 24 | 示例 2: 25 | 26 | ![4E2FE863C450BC15](http://cdn.heroxu.com/4E2FE863C450BC15.png) 27 | 28 | ``` 29 | 输入:root = [1,2,3,null,4,null,5], x = 5, y = 4 30 | 输出:true 31 | ``` 32 | 33 | 34 | 示例 3: 35 | 36 | ![B1531D93BAB7F4F9](http://cdn.heroxu.com/B1531D93BAB7F4F9.png) 37 | 38 | ``` 39 | 输入:root = [1,2,3,null,4], x = 2, y = 3 40 | 输出:false 41 | ``` 42 | 43 | **提示:** 44 | 45 | - 二叉树的节点数介于 2 到 100 之间。 46 | - 每个节点的值都是唯一的、范围为 1 到 100 的整数。 47 | 48 | 49 | ![5267559A1E0FF9B7](http://cdn.heroxu.com/5267559A1E0FF9B7.png) 50 | 51 | 52 | ``` 53 | class Solution( 54 | public boolean isCousins(TreeNode root, int x, int y){ 55 | int[] xi = dfs(root, null, 0, x); 56 | int[] yi = dfs(root, null, 0 ,y); 57 | return xi[1] == yi[1] && xi[0] != yi[0]; 58 | } 59 | 60 | public int[] dfs(TreeNode root, TreeNode father, int depth, int t){ 61 | if(root == null) { 62 | return new int[-1,-1]; 63 | } 64 | if(root.val == t){ 65 | return new int[]{fa != null ? fa.val : 1, depth}; 66 | } 67 | int[] l=dfs(root.left, root,depth+1,t); 68 | if(l[0]!=-1){ 69 | retrun l; 70 | }else{ 71 | return dfs(root.right, root,depth+1,t); 72 | } 73 | 74 | } 75 | ) 76 | ``` -------------------------------------------------------------------------------- /LeetCode/数据结构/二叉树/s11020707042022.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MyHerux/code-note/854b0f86d319886564c00c21040a1745f88b10a2/LeetCode/数据结构/二叉树/s11020707042022.png -------------------------------------------------------------------------------- /LeetCode/数据结构/二叉树/s11341507042022.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MyHerux/code-note/854b0f86d319886564c00c21040a1745f88b10a2/LeetCode/数据结构/二叉树/s11341507042022.png -------------------------------------------------------------------------------- /LeetCode/链表/21_合并两个有序链表.md: -------------------------------------------------------------------------------- 1 | 2 | ## 题目描述 3 | 4 | 将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。 5 | 6 | 示例一: 7 | 8 | ``` 9 | 输入:l1 = [1,2,4], l2 = [1,3,4] 10 | 输出:[1,1,2,3,4,4] 11 | ``` 12 | 13 | 示例二: 14 | 15 | ``` 16 | 输入:l1 = [], l2 = [] 17 | 输出:[] 18 | ``` 19 | 20 | 示例三: 21 | 22 | ``` 23 | 输入:l1 = [], l2 = [0] 24 | 输出:[0] 25 | ``` 26 | 27 | 28 | ## 解法 29 | 30 | 递归: 31 | 32 | ``` 33 | class Solution { 34 | public ListNode mergeTwoLists(ListNode l1, ListNode l2) { 35 | 36 | if(l1 == null){ 37 | return l2; 38 | }else if(l2 == null){ 39 | return l1; 40 | }else if(l1.val pq; 48 | int k; 49 | 50 | public KthLargest(int k, int[] nums) { 51 | this.k = k; 52 | pq = new PriorityQueue(); 53 | for (int x : nums) { 54 | add(x); 55 | } 56 | } 57 | 58 | public int add(int val) { 59 | pq.offer(val); 60 | if (pq.size() > k) { 61 | pq.poll(); 62 | } 63 | return pq.peek(); 64 | } 65 | } 66 | ``` 67 | 68 | ## 刷题记录 69 | 70 | - 2021-04-07 15:17 71 | 72 | -------------------------------------------------------------------------------- /LeetCode/队列/优先队列.md: -------------------------------------------------------------------------------- 1 | ## 优先队列 2 | 3 | 普通队列遵循先进先出 4 | 5 | ![20210407110048](http://cdn.heroxu.com/20210407110048.png) 6 | 7 | ![20210407110118](http://cdn.heroxu.com/20210407110118.png) 8 | 9 | 优先队列不再遵循先入先出的原则,而是分为两种情况: 10 | 11 | ``` 12 | 最大优先队列,无论入队顺序,当前最大的元素优先出队。 13 | 最小优先队列,无论入队顺序,当前最小的元素优先出队。 14 | ``` 15 | 16 | ![20210407110537](http://cdn.heroxu.com/20210407110537.png) 17 | 18 | 要满足以上需求,利用线性数据结构并非不能实现,但是时间复杂度较高,最坏时间复杂度O(n),并不是最理想的方式。 19 | 20 | 回顾一下二叉堆的特性: 21 | 22 | - 1.最大堆的堆顶是整个堆中的最大元素 23 | 24 | - 2.最小堆的堆顶是整个堆中的最小元素 25 | 26 | 因此,我们可以用最大堆来实现最大优先队列,每一次入队操作就是堆的插入操作,每一次出队操作就是删除堆顶节点。 27 | 28 | **入队操作:** 29 | 30 | 1. 插入新节点5 31 | 32 | ![20210407145511](http://cdn.heroxu.com/20210407145511.png) 33 | 34 | 2. 新节点5上浮到合适位置。 35 | 36 | ![20210407145533](http://cdn.heroxu.com/20210407145533.png) 37 | 38 | **出队操作:** 39 | 40 | 1. 把原堆顶节点10“出队” 41 | 42 | ![20210407145646](http://cdn.heroxu.com/20210407145646.png) 43 | 44 | 2. 最后一个节点1替换到堆顶位置 45 | 46 | ![20210407145656](http://cdn.heroxu.com/20210407145656.png) 47 | 48 | 3. 节点1下沉,节点9成为新堆顶 49 | 50 | ![20210407145706](http://cdn.heroxu.com/20210407145706.png) 51 | 52 | 53 | ## 参考 54 | 55 | - [漫画:什么是优先队列?](https://www.cxyxiaowu.com/5417.html) -------------------------------------------------------------------------------- /Program/工具篇/Yapi/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:11 as builder 2 | 3 | RUN apt-get install -y git python make openssl tar gcc 4 | ADD yapi.tgz /home/ 5 | RUN mkdir /api && mv /home/package /api/vendors 6 | RUN cd /api/vendors && \ 7 | npm install --production --registry https://registry.npm.taobao.org 8 | 9 | FROM node:11 10 | 11 | MAINTAINER hua.xu 12 | ENV TZ="Asia/Shanghai" HOME="/" 13 | WORKDIR ${HOME} 14 | 15 | COPY --from=builder /api/vendors /api/vendors 16 | COPY config.json /api/ 17 | EXPOSE 3001 18 | 19 | COPY docker-entrypoint.sh /api/ 20 | RUN chmod 755 /api/docker-entrypoint.sh 21 | 22 | ENTRYPOINT ["/api/docker-entrypoint.sh"] -------------------------------------------------------------------------------- /Program/工具篇/Yapi/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "port": "MY_PORT", 3 | "adminAccount": "MY_ACOUNT", 4 | "db": { 5 | "servername": "MY_DB_SERVER", 6 | "DATABASE": "MY_DB_NAME", 7 | "port": "MY_DB_PORT", 8 | "user": "MY_USER", 9 | "pass": "MY_PASS", 10 | "authSource": "MY_AUTH" 11 | } 12 | } -------------------------------------------------------------------------------- /Program/工具篇/Yapi/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.7' 2 | networks: 3 | netdci: 4 | 5 | services: 6 | yapi: 7 | build: 8 | context: ./ 9 | dockerfile: ./Dockerfile 10 | image: skycitygalaxy/yapi 11 | container_name: yapi 12 | environment: 13 | - VERSION=1.8.1 14 | - LOG_PATH=/tmp/yapi.log 15 | - HOME=/home 16 | - MY_PORT=3001 17 | - MY_ACOUNT=heroxu123@gmail.com 18 | - MY_DB_SERVER=127.0.0.1 19 | - MY_DB_NAME=yapi 20 | - MY_DB_PORT=27027 21 | - MY_USER=xu 22 | - MY_PASS=xu 23 | - MY_AUTH=test 24 | ports: 25 | - 127.0.0.1:3000:3000 26 | volumes: 27 | - ~/data/yapi/log/yapi.log:/home/vendors/log # log dir 28 | depends_on: 29 | - mongo 30 | networks: 31 | - netdci 32 | 33 | mongo: 34 | image: mongo 35 | container_name: mongo 36 | ports: 37 | - 127.0.0.1:27027:27017 38 | volumes: 39 | - ~/data/yapi/mongodb:/data/db #db dir 40 | networks: 41 | - netdci 42 | -------------------------------------------------------------------------------- /Program/工具篇/Yapi/docker-entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -eo pipefail 3 | shopt -s nullglob 4 | 5 | MY_PORT="${MY_PORT:=3000}" 6 | MY_ACOUNT="${MY_ACOUNT:=heroxu123@gmail.com}" 7 | MY_DB_SERVER="${MY_DB_SERVER:=127.0.0.1}" 8 | MY_DB_NAME="${MY_DB_NAME:=yapi}" 9 | MY_DB_PORT="${MY_DB_PORT:=27027}" 10 | MY_USER="${MY_USER:=xu}" 11 | MY_PASS="${MY_PASS:=xu}" 12 | MY_AUTH="${MY_AUTH:=test}" 13 | 14 | config() { 15 | if [[ -z "${MY_PORT}" || -z "${MY_ACOUNT}" || -z "${MY_DB_SERVER}" || -z "${MY_DB_NAME}" || -z "${MY_DB_PORT}" || -z "${MY_USER}" || -z "${MY_PASS}" || -z "${MY_AUTH}" ]]; then 16 | echo -e "\n\"MY_PORT\" or \"MY_ACOUNT\" or \"MY_DB_SERVER\" or \"MY_DB_NAME\" or \"MY_DB_PORT\" or \"MY_USER\" or \"MY_PASS\" or \"MY_AUTH\" can not be empty!\n" && exit 1 17 | else 18 | sed -i "s#MY_PORT#${MY_PORT}#g" /api/config.json 19 | sed -i "s#MY_ACOUNT#${MY_ACOUNT}#g" /api/config.json 20 | sed -i "s#MY_DB_SERVER#${MY_DB_SERVER}#g" /api/config.json 21 | sed -i "s#MY_DB_NAME#${MY_DB_NAME}#g" /api/config.json 22 | sed -i "s#MY_DB_PORT#${MY_DB_PORT}#g" /api/config.json 23 | sed -i "s#MY_USER#${MY_USER}#g" /api/config.json 24 | sed -i "s#MY_PASS#${MY_PASS}#g" /api/config.json 25 | sed -i "s#MY_AUTH#${MY_AUTH}#g" /api/config.json 26 | 27 | fi 28 | } 29 | 30 | config 31 | 32 | node /api/vendors/server/app.js 33 | 34 | exec "$@" -------------------------------------------------------------------------------- /Program/工具篇/Yapi/download.sh: -------------------------------------------------------------------------------- 1 | function usage(){ 2 | echo "usage: sh build.sh " 3 | echo "默认版本: 1.7.1" 4 | echo "yapi的版本: https://github.com/YMFE/yapi/releases" 5 | echo "我们将从这里下载: http://registry.npm.taobao.org/yapi-vendor/download/yapi-vendor-\$1.tgz" 6 | } 7 | 8 | 9 | 10 | version=1.7.1 11 | 12 | if [ -n "$1" ]; then 13 | version=$1 14 | fi 15 | 16 | usage 17 | 18 | 19 | echo -e "\033[32m download new package (version $version) \033[0m" 20 | 21 | wget -O yapi.tgz http://registry.npm.taobao.org/yapi-vendor/download/yapi-vendor-$version.tgz 22 | -------------------------------------------------------------------------------- /Program/工具篇/Yapi/yapi.tgz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MyHerux/code-note/854b0f86d319886564c00c21040a1745f88b10a2/Program/工具篇/Yapi/yapi.tgz -------------------------------------------------------------------------------- /Program/工具篇/Yapi/使用DockerCompose构建部署Yapi.md: -------------------------------------------------------------------------------- 1 | # 使用DockerCompose构建部署Yapi 2 | 3 | ## OverView 4 | 5 | YApi 是一个可本地部署的、打通前后端及QA的、可视化的接口管理平台 https://hellosean1025.github.io/yapi 6 | 7 | ## 准备一个自己的 Mongo 8 | 9 | 因为这些数据都是要落地的,建议单独准备一个DB。 10 | 11 | ## 自己构建镜像 12 | 13 | 自己构建的镜像可以保证镜像的安全,或者可以魔改一下代码再构建镜像。 14 | 15 | - 下载 Yapi 16 | 17 | ``` 18 | ./download.sh 1.9.3 19 | ``` 20 | 21 | - 构建镜像 22 | 23 | ``` 24 | docker-compose build 25 | ``` 26 | 27 | - Push 镜像 28 | 29 | ``` 30 | docker tag skycitygalaxy/yapi:latest skycitygalaxy/yapi:v1.9.3 31 | docker push skycitygalaxy/yapi:v1.9.3 32 | ``` 33 | 34 | ## 直接使用镜像,本地部署 35 | 36 | 如果不想自己构建镜像的话,可以使用我打包好的镜像:skycitygalaxy/yapi:v1.9.3 37 | 38 | - 拉取镜像 39 | 40 | ``` 41 | docker pull skycitygalaxy/yapi:v1.9.3 42 | ``` 43 | 44 | - 启动服务 45 | 46 | ``` 47 | docker run -d -p 3001:3000 --name yapi skycitygalaxy/yapi:v1.9.3 48 | ``` 49 | 50 | - 修改配置 51 | 52 | 进入容器,修改配置为自己的配置。 53 | 54 | ``` 55 | docker exec -ti yapi bash 56 | cd /api/ 57 | vim config.json 58 | ``` 59 | 60 | - 重启服务 61 | 62 | ``` 63 | docker restart yapi 64 | ``` 65 | 66 | - 访问 http://127.0.0.1:3001/ 67 | 68 | ![](http://cdn.heroxu.com/2019080815652468118063.png) 69 | 70 | ## 使用 Rancher 部署 71 | 72 | - 配置环境变量 73 | 74 | ![](http://cdn.heroxu.com/20190808156524572847385.png) 75 | 76 | - 部署完成 77 | 78 | ![](http://cdn.heroxu.com/20190808156524588021590.png) 79 | 80 | ![](http://cdn.heroxu.com/20190808156524581769215.png) 81 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # code-note 2 | 3 | 不积跬步,无以至千里;不积小流,无以成江海。 4 | 5 | 记录编程的点点滴滴。 6 | -------------------------------------------------------------------------------- /剑指Offer/1_斐波那契数列/README.md: -------------------------------------------------------------------------------- 1 | # 斐波那契数列 2 | 3 | ## 题目描述 4 | 5 | 大家都知道斐波那契数列,现在要求输入一个整数 n,请你输出斐波那契数列的第 n 项(从 0 开始,第 0 项为 0,第 1 项是 1)。 6 | n<=39 7 | 8 | ## 菲波那切数列是什么 9 | 10 | ``` 11 | 0 1 1 2 3 5 8 13 12 | 13 | n = 0, num = 0 14 | n = 1, num = 1 15 | n = 2, num = 1 16 | ..... 17 | 18 | 当 n = k(n>1),f(k) = f(k-1) + f(k-2) 19 | 当 n = 1, f(1) = 1 20 | 当 n = 0, f(0) = 0 21 | ``` 22 | 23 | ## 递归方法实现 24 | 25 | ``` 26 | public class Solution { 27 | public int Fibonacci(int n) { 28 | if(n==0){ 29 | return 0; 30 | }else if(n==1){ 31 | return 1; 32 | }else if(n>1){ 33 | return Fibonacci(n-1)+Fibonacci(n-2); 34 | }else{ 35 | return 0; 36 | } 37 | } 38 | } 39 | ``` 40 | 41 | ### 存在问题 42 | 43 | 随着 n 提升,时间复杂度不断增加,时间复杂度:`2^n` 44 | 45 | ![20200701145656](http://cdn.heroxu.com/20200701145656.png) 46 | 47 | 我们发现对于某个值,我们重复计算了很多遍,是否可以不重复计算这些值? 48 | 49 | ## 非递归实现 50 | 51 | 我们继续查看刚刚的规律 52 | 53 | ``` 54 | 当 n = 2的时候, h = f(1) + f(0) = 1 + 0 = 1 55 | 当 n = 3的时候, h = f(2) + f(1) = 1 + 1 = 2 56 | 当 n = 4的时候, h = f(3) + f(2) = 2 + 1 = 3 57 | ``` 58 | 59 | 我们可以从 n = 2,一直循环,直到我们计算出我们输入的值为 n 的时候 60 | 61 | 我们只需要从最小的开始计算,每次保留中间结果,最后得出我们的第 n 个的结果。 62 | 63 | ``` 64 | public class Solution { 65 | public int Fibonacci(int n) { 66 | if(n==0){ 67 | return 0; 68 | }else if(n==1){ 69 | return 1; 70 | }else if(n>1){ 71 | int a=0; 72 | int b=1; 73 | int ret=0; 74 | for(int i=0;i 7,因此可能就在这一行,左移,最终找到了7 59 | ``` 60 | 61 | 实现: 62 | 63 | ``` 64 | public class Solution { 65 | public boolean Find(int target, int [][] array) { 66 | if (array == null || array.length == 0 || array[0].length == 0) { 67 | return false; 68 | } 69 | int m=0; 70 | int n=array[0].length-1; 71 | int tem=array[m][n]; 72 | while(tem!=target){ 73 | if((m0)){ 74 | if(tem>target){ 75 | n--; 76 | }else{ 77 | m++; 78 | } 79 | tem=array[m][n]; 80 | }else{ 81 | return false; 82 | } 83 | 84 | } 85 | return true; 86 | } 87 | } 88 | ``` -------------------------------------------------------------------------------- /剑指Offer/4_空格的替换/README.md: -------------------------------------------------------------------------------- 1 | # 替换空格 2 | 3 | ## 题目描述 4 | 5 | 请实现一个函数,将一个字符串中的每个空格替换成“%20”。例如,当字符串为We Are Happy.则经过替换之后的字符串为We%20Are%20Happy。 6 | 7 | ## 调用自带的函数 8 | 9 | ``` 10 | public class Solution { 11 | public String replaceSpace(StringBuffer str) { 12 | return str.toString().replace(" ","%20"); 13 | } 14 | } 15 | ``` 16 | 17 | ## 用新的字符串来存储 18 | 19 | ``` 20 | public class Solution { 21 | public String replaceSpace(StringBuffer str) { 22 | StringBuffer newStr = new StringBuffer(); 23 | for(int i=0;i= 0 && oldStrIndex stack1 = new Stack(); 24 | Stack stack2 = new Stack(); 25 | 26 | public void push(int node) { 27 | stack1.push(node); 28 | } 29 | 30 | public int pop() { 31 | if(stack2.empty()){ 32 | while(!stack1.empty()){ 33 | stack2.push(stack1.pop()); 34 | } 35 | } 36 | return stack2.pop(); 37 | } 38 | } 39 | ``` -------------------------------------------------------------------------------- /剑指Offer/README.md: -------------------------------------------------------------------------------- 1 | # 剑指Offer 2 | 3 | 添加剑指Offer题目部分。 -------------------------------------------------------------------------------- /研发相关/DevOps/CI-CD/README.md: -------------------------------------------------------------------------------- 1 | ## CI/CD 2 | 3 | - [从0到1,完整的CI-CD流程](./从0到1,完整的CI-CD流程.md) 4 | 5 | - [使用Docker-Compose组合多个镜像](./使用Docker-Compose组合多个镜像.md) 6 | 7 | - [使用Jenkins实现自动部署](./使用Jenkins实现自动部署.md) 8 | 9 | - [提交镜像到DockerHub](./提交镜像到DockerHub.md) 10 | -------------------------------------------------------------------------------- /研发相关/DevOps/CI-CD/使用Maven和WinSCP命令自动打包上传.md: -------------------------------------------------------------------------------- 1 | # 使用Maven和WinSCP命令自动打War包上传 2 | 3 | ## Windows批处理命令 4 | 5 | - ECHO 和 @ 6 | 7 | - 打开回显或关闭回显功能 8 | 9 | > 格式:`echo [{ on|off }]`。如果想关闭“ECHO OFF”命令行自身的显示,则需要在该命令行前加上“@”。 10 | 11 | - 显示当前ECHO设置状态 12 | 13 | > 格式: `echo` 14 | 15 | - 输出提示信息 16 | 17 | > 格式:`echo 信息内容` 18 | 19 | - PAUSE 20 | 21 | - 停止系统命令的执行并显示 22 | 23 | > 格式:`pause` 响应:请按任意键继续. . . 24 | 25 | - CALL 26 | 27 | - 批处理执行过程中调用另一个批处理,当另一个批处理执行完后,再继续执行原来的批处理 28 | 29 | > 格式:`call command` 30 | 31 | - 更多命令 32 | 33 | [参考](http://www.cnblogs.com/iTlijun/p/6137027.html) 34 | 35 | ## WinSCP 36 | 37 | - 下载 38 | 39 | - 修改环境变量 40 | 41 | - 命令 42 | 43 | - call 执行任意远程Shell命令 44 | - cd 改变远程工作目录 45 | - chmod 改变远程文件权限 46 | - close 关闭会话 47 | - exit 关闭所有会话并结束程序 48 | - get 从远程目录下载文件到本地目录 49 | - help 显示帮助 50 | - keepuptodate 在一个远程目录连续反映本地目录的改变 51 | - lcd 改变本地工作目录 52 | - lls 列出本地目录的内容 53 | - ln 新建远程符号链接 54 | - lpwd 显示本地工作目录 55 | - ls 列出远程目录的内容 56 | - mkdir 新建远程目录 57 | - mv 移动或者重命名远程文件 58 | - open 连接到服务器 59 | - option 设置或显示脚本选项的值 60 | - put 从本地目录上传文件到远程目录 61 | - pwd 显示远程工作目录 62 | - rm 删除远程文件 63 | - rmdir 删除远程目录 64 | - session 列出连接的会话或者选择活动会话 65 | - synchronize 用一个本地目录同步远程目录 66 | 67 | ## Maven命令 68 | 69 | - mvn clean 70 | > 清除先前构建的 `artifacts` 71 | - mvn validate 72 | > 验证工程是否正确,所有需要的资源是否可用 73 | - mvn compile 74 | > 编译项目的源代码 75 | - mvn test 76 | > 使用合适的单元测试框架来测试已编译的源代码。这些测试不需要已打包和布署。 77 | - mvn package 78 | > 把已编译的代码打包成可发布的格式 79 | - mvn verify 80 | > 运行所有检查,验证包是否有效且达到质量标准 81 | - mvn install 82 | > 把包安装在本地的repository中,可以被其他工程作为依赖来使用。 83 | - mvn site 84 | > 为项目生成文档站点。 85 | - mvn deploy 86 | > 在集成或者发布环境下执行,将最终版本的包拷贝到远程的repository,使得其他的开发者或者工程可以共享。 87 | - mvn war:war 88 | > 插件命令,将项目打成war包 89 | 90 | ## Bat脚本 91 | 92 | 使用start.bat,先用mvn打包,然后再调用upload.bat上传war包。 93 | 94 | - start.bat 95 | 96 | ``` 97 | echo 98 | e: 99 | cd \Blacklist\Pcredit 100 | call mvn install -DskipTests=true 101 | pause 102 | call WinSCP.com /script=\Blacklist\Pcredit\upload.bat 103 | pause 104 | ``` 105 | - upload.bat 106 | 107 | ``` 108 | option confirm off 109 | open user:pwd@服务器ip 110 | put E:\Blacklist\Pcredit\target\credit.war 111 | close 112 | exit 113 | ``` -------------------------------------------------------------------------------- /研发相关/DevOps/CI-CD/提交镜像到DockerHub.md: -------------------------------------------------------------------------------- 1 | # 提交镜像到DockerHub 2 | 3 | - pre 4 | 5 | 注册 `dockerhub` 账号 6 | 7 | - 登录 8 | 9 | ``` 10 | docker login 11 | ``` 12 | 13 | ![](http://cdn.heroxu.com/20190801156465366122767.png) 14 | 15 | - 查看当前镜像,选择一个比较小的image 16 | 17 | ``` 18 | docker images|grep node 19 | ``` 20 | 21 | ![](http://cdn.heroxu.com/20190801156465369750974.png) 22 | 23 | - 标记本地镜像,将其归入远程仓库 24 | 25 | ``` 26 | docker tag node:9.11.1-alpine skycitygalaxy/node:v1 27 | ``` 28 | 29 | - push镜像 30 | 31 | ``` 32 | docker push skycitygalaxy/node 33 | ``` 34 | 35 | ![](http://cdn.heroxu.com/2019080115646542367565.png) 36 | 37 | - 查看仓库中的镜像 38 | 39 | ![](http://cdn.heroxu.com/20190801156465945393031.png) -------------------------------------------------------------------------------- /研发相关/DevOps/敏捷开发/Swagger下的前后端协作.md: -------------------------------------------------------------------------------- 1 | # Swagger下的前后端协作 2 | 3 | ## Swagger的使用 4 | 5 | - 官方网站 6 | 7 | > [Github-Swagger-samples](https://github.com/swagger-api/swagger-samples) 8 | 9 | - 引入依赖 10 | 11 | ``` 12 | 13 | io.springfox 14 | springfox-swagger2 15 | 2.7.0 16 | 17 | 18 | io.springfox 19 | springfox-swagger-ui 20 | 2.7.0 21 | 22 | ``` 23 | 24 | - 开启注解 25 | 26 | > 在 `Application` 上添加注解 `@EnableSwagger2` 27 | 28 | ## 使用范围 29 | 30 | - 所有前后端分离的接口 31 | 32 | >满足契约测试标准,后端所有接口信息通过注解定义。前端通过swagger测试接口与开发。 33 | 34 | - 所有api接口(内部使用的) 35 | 36 | ## 具体使用 37 | 38 | - 需要注解的字段 39 | 40 | - `ApiOperation` 41 | 42 | > @ApiOperation(value = "风险预警-设置阈值", notes = risk) 43 | 44 | 因为使用前后端分离,推荐所有参数为 `json格式` ,所以在 `notes` 里面注明 `需要 Mock 的静态输入`。 45 | - `ApiResponse` 46 | 47 | > 返回的状态码与数据 48 | 49 | - 使用方式 50 | 51 | 前端访问 `http://localhost:8080/swagger-ui.html` ,即可使用所有的 `Mock接口`。 52 | 53 | ## 开发模式 54 | 55 | > 虽然原则上在设计之初就应该定好接口的输入输出,实际开发中仍然无法避免接口变动。 56 | 57 | - 明确的输入输出 58 | 59 | 对于明确的接口,后端可以优先定义好接收的方法参数和输出的json,通过静态变量的方式提供mock数据给前端以供前端开发。 60 | 61 | - 不明确的接口 62 | 63 | 对于不明确的接口,建议直接开发,在开发的过程中掌握细节,提供稳定的完整接口给前端。 -------------------------------------------------------------------------------- /研发相关/DevOps/敏捷开发/使用Worktile来打造高效的团队.md: -------------------------------------------------------------------------------- 1 | # 使用Worktile来打造高效的团队 2 | 3 | > 本文章使用的是企业版和普通版有些许不同,不过敏捷思想是一样的 4 | 5 | ## 高效团队的定义 6 | 7 | - 需求的明确定义与拆分 8 | 9 | - 新任务的敏捷开发 10 | 11 | - 已完成任务的快速验收,问题任务的反馈 12 | 13 | > 以上三点已经包括一个开发团队的全部:`产品经理`,`开发人员`,`测试` 14 | 15 | 16 | ## 需求的明确定义与拆分 17 | 18 | - 需求的定义 19 | 20 | 现在需要写一篇文章《使用Worktile来打造高效的团队》 21 | 22 | ![mark](http://of0qa2hzs.bkt.clouddn.com/blog/171127/gdgCKGdEKc.png?imageslim) 23 | 24 | - 需求的拆分 25 | 26 | - 现在我们将需求拆分为4个放到需求池里面: 27 | 28 | > 需求的拆分尽量保持功能之间 `划分清晰`,工作量尽量符合 `3天原则` ,保证在 `3天内` 。 29 | 30 | ![mark](http://of0qa2hzs.bkt.clouddn.com/blog/171127/D3BGJJi4ke.png?imageslim) 31 | 32 | - 每个任务的详细描述 33 | 34 | > 对于每个具体任务,尽量说明要点,附上文档。 35 | 36 | ![mark](http://of0qa2hzs.bkt.clouddn.com/blog/171127/1jff1kGG9H.png?imageslim) 37 | 38 | - 标签的定义 39 | 40 | > 我们使用三种标签来区分任务,方便统计。 41 | 42 | - `Feat`:新的需求,新的功能点。 43 | - `Fix`:旧的功能点出现问题,需要修复的 `bug`。 44 | - `Refactor`: 旧的功能点,需要重构的部分。 45 | - 标签与 `git commit` 的使用可以参考 [Git的使用](https://github.com/MyHerux/program-beginer/blob/master/2.md) 46 | 47 | ![mark](http://of0qa2hzs.bkt.clouddn.com/blog/171127/4be2Ffci2E.png?imageslim) 48 | 49 | ## 新任务的敏捷开发 50 | 51 | - 任务的接取 52 | 53 | > 需求池中的需求被相关程序员接取,原则上应该保证不让自己负责的任务跨度过大。 54 | 55 | - 将任务划分到自己下面。 56 | 57 | - 确定开始时间和截止时间,如果对于任务完成时间(超过 `3天原则` )有异议的应当及时找产品经理确定。 58 | 59 | - 新建子任务,将3天的任务进一步细分。 60 | 61 | ![mark](http://of0qa2hzs.bkt.clouddn.com/blog/171127/FiJ5GjhhFK.png?imageslim) 62 | 63 | - 任务状态的变更 64 | 65 | > 完成后的任务需要变更其任务组(可以自己设置更合适的分组),方便观察每天的任务变动情况 66 | 67 | - 接取的任务应该划分到《在做》 68 | 69 | - 完成后的任务在子任务中添加划分的测试人员,然后划分到《测试》 70 | 71 | - 测试验收后的任务划分到《完成》 72 | 73 | ![mark](http://of0qa2hzs.bkt.clouddn.com/blog/171127/e6Ceg4A707.png?imageslim) 74 | 75 | ![mark](http://of0qa2hzs.bkt.clouddn.com/blog/171127/2aD0JGbbg6.png?imageslim) 76 | 77 | ![mark](http://of0qa2hzs.bkt.clouddn.com/blog/171127/Dc82cCL5Ef.png?imageslim) 78 | 79 | ## 已完成任务的快速验收,问题任务的反馈 80 | 81 | - 任务验收 82 | 83 | > 对于每个划分到《测试》组的任务进行验收 84 | 85 | - 未通过任务标注具体信息,@任务人 86 | 87 | - 收到消息的任务人会修改标注的错误重新提交 88 | 89 | - 重复验收过程,通过后的任务划分任务到《完成》 90 | 91 | ![mark](http://of0qa2hzs.bkt.clouddn.com/blog/171127/DlL53fdD11.png?imageslim) 92 | 93 | - `Bug` 反馈 94 | 95 | > 回归测试中的 `Bug` 需要反馈到需求池 96 | 97 | ![mark](http://of0qa2hzs.bkt.clouddn.com/blog/171127/EbaA8EeEAf.png?imageslim) 98 | 99 | ## 统计与分析 100 | 101 | - 甘特图 102 | 103 | > 通过甘特图来分析任务的跨度与进行状态 104 | 105 | ![mark](http://of0qa2hzs.bkt.clouddn.com/blog/171127/9mg78gHLGj.png?imageslim) 106 | 107 | - 任务统计 108 | 109 | > 通过任务统计来分析任务完成状况 110 | 111 | ![mark](http://of0qa2hzs.bkt.clouddn.com/blog/171127/BA7272bII9.png?imageslim) -------------------------------------------------------------------------------- /研发相关/DevOps/敏捷开发/程序员的自我修养.md: -------------------------------------------------------------------------------- 1 | # 程序员的自我修养 2 | 3 | ## 基本的原则 4 | 5 | - 一切以解决问题为导向 6 | 7 | 很多时候大家做事是以问题为导向的:出了问题先找是谁负责的,把责任推给别人(俗称甩锅)。实际开发中,很多项目都是互相关联的。我们确实能找到一个主要负责人来负责这个问题,但是这并不能解决问题。正确的做法是 `先把问题解决`,尽量减少损失,然后再去排查这个问题发生的经过和原因(并不是一定要追究谁的责任,而是为了吸取教训)。 8 | 我和同事的相处也渡过了类似的时期,大家逐渐把关注点放到解决问题上之后。 9 | 10 | - 一是问题得到迅速的解决 11 | 12 | - 二是避免了很多无意义的推诿 13 | 14 | - 三是大家会更加谨慎的对待项目(每次教训都会被记录下来)。 15 | 16 | - 对合作伙伴的尊重与包容 17 | 18 | 每个人的水平必然是不相同,大家各有擅长,并且在不断进步中。所以遇到同事或者合作的友商犯了比较低级(对你来说可能是)的错误是很正常的事情,我们更多的是应该给与 `尊重与包容`,而不是嘲笑或者消极对待。 19 | 20 | 在不断的鼓励同事和合作伙伴之后,能感受到大家对我的信赖,特别是和友商的合作变得非常的愉快,大家互相尊重,解决问题。 21 | 22 | - 更多的正反馈 23 | 24 | 程序员工作的乐趣在于:你能很清楚的知道你在不断的进步不断的变强。但是感觉是最无法量化的,所以需要给自己更多的 `正反馈`:把自己的项目做得更好,把自己的经验写成博客,在 `github` 上维护开源项目等等。 25 | 26 | ## 工程师素养 27 | 28 | - 对于技术的不断追求 29 | 30 | 身为一个工程师,主要便是跟技术打交道,只有掌握了基本的技术,才能算作一个合格的工程师。 31 | 然而学无止境,技术日新月异,不停的有好技术出现,不好的技术被淘汰。 32 | 我们如何保证自己不被淘汰,甚至优于常人:比大部分人更牛逼,拿更高的工资,做出更好的产品。 33 | 我想只有通过不断的学习,不断的追求技术,方能使自己在技术方面更优秀。 34 | 保持危机感,适当的脱离舒适区,都有助于自己的成长。经常想想如果自己一直不学习不成长,几年后我还能胜任更好的工作吗? 35 | 有一句话叫 `知行合一`,如何检验一个人是否真的知晓了一个道理的标准就是:他有没有这样去做。 36 | 37 | - 对事业的热爱 38 | 39 | 有人想问什么叫热爱自己的事业? 40 | 我觉得首先`事业`的定义应该是:能让你感到快乐的工作。 41 | 什么样的工作能让你感到快乐呢?我认为最重要的一点是主动。主动去把事情做得最好,收获`荣誉感`和`成就感`,这样的工作就是你的事业。 42 | 单纯的为了工作而工作肯定不能让你感到快乐,然而为了自己的荣誉感和成就感则能让你很容易感受到快乐。 43 | 每次任务到了我这里我从来不会接口推辞,任何困难的任务在我看来都是一次挑战,我战胜了困难,自然给我很大的`成就感`。即使是简单的任务也给自己制定更有挑战的目标,更快完成,代码质量更高。 44 | 与此同时,项目的圆满完成,老大的夸奖也带来了荣誉感。希望大家在代码中收获自己的`成就感`,在项目中收获自己的`荣誉感`。 45 | 如果说我每天做的事情都给我带来快乐,我有什么理由不去热爱他呢。 46 | 47 | -------------------------------------------------------------------------------- /研发相关/DevOps/测试/JMeter压测.md: -------------------------------------------------------------------------------- 1 | # JMeter压测 2 | 3 | ## 准备 4 | 5 | - 下载地址 6 | 7 | [Download Apache JMeter](http://jmeter.apache.org/download_jmeter.cgi) 8 | 9 | - 打开批处理文件 10 | 11 | ![mark](http://of0qa2hzs.bkt.clouddn.com/blog/180131/iL86eacGGd.png?imageslim) 12 | 13 | ![mark](http://of0qa2hzs.bkt.clouddn.com/blog/180131/GDlG687Ke9.png?imageslim) 14 | 15 | ## 基本配置 16 | 17 | - 添加线程组 18 | 19 | 测试计划右键->添加->threads(Users)->线程组 20 | 21 | ![mark](http://of0qa2hzs.bkt.clouddn.com/blog/180131/gA87CG7A3l.png?imageslim) 22 | 23 | - 添加HTTP请求默认值 24 | 25 | 线程组上右键->添加->配置元件->HTTP请求默认值 26 | 27 | ![mark](http://of0qa2hzs.bkt.clouddn.com/blog/180131/GE5BKC4EF8.png?imageslim) 28 | 29 | - 添加HTTP信息头 30 | 31 | 线程组上右键->添加->配置元件->HTTP信息头管理器 32 | 33 | ![mark](http://of0qa2hzs.bkt.clouddn.com/blog/180131/g4LeECiDGe.png?imageslim) 34 | 35 | - http请求构造 36 | 37 | 线程组上右键->添加->samlper->HTTP请求 38 | 39 | ![mark](http://of0qa2hzs.bkt.clouddn.com/blog/180131/fKI24d7B8i.png?imageslim) 40 | 41 | - 查看结果树 42 | 43 | 线程组上右键->添加->监听器->查看结果树、聚合报告 44 | 45 | ![mark](http://of0qa2hzs.bkt.clouddn.com/blog/180131/IHLI8AhJL1.png?imageslim) 46 | 47 | ![mark](http://of0qa2hzs.bkt.clouddn.com/blog/180131/0J9Dhl2h44.png?imageslim) 48 | 49 | 聚合报告: 50 | 51 | - Samples:发出请求数量。如第三行记录,模拟20个用户,循环100次,所以显示了2000 52 | 53 | - Average:平均响应时间(单位:)。默认是单个Request的平均响应时间,当使用了Transaction Controller时,也可以以Transaction为单位显示平均响应时间 54 | 55 | - Median:中位数,也就是50%用户的响应时间 56 | 57 | - 90%Line:90%用户的响应时间 58 | 59 | - 95%Line:95%用户的响应时间 60 | 61 | - 99%Line:99%用户的响应时间 62 | 63 | - Min:最小响应时间 64 | 65 | - Max:最大响应时间 66 | 67 | - Error%:本次测试中出现错误的请求的数量/请求的总数 68 | 69 | - Throughput:吞吐量。默认情况下标示每秒完成的请求数(具体单位如下图) 70 | 71 | - KB/sec:每秒从服务器端接收到的数据量。 72 | 73 | ## 额外的插件 74 | 75 | - 插件下载 76 | 77 | [jmeter-plugins.org ](https://jmeter-plugins.org/) 78 | 79 | - TPS插件 80 | 81 | [JMeter 每秒事务数 TPS 插件](https://jmeter-plugins.org/wiki/TransactionsPerSecond/) 82 | -------------------------------------------------------------------------------- /研发相关/JAVA基础/JVM/Java内存模型(JMM).md: -------------------------------------------------------------------------------- 1 | # Java内存模型(JMM) 2 | 3 | ## 原子性(Atomicity) 4 | 5 | > 原子性是指一个操作是不可中断的。即使是多个线程一起执行的时候,一个操作一旦开始,就不会被其他线程干扰。 6 | 7 | 比如,一个静态全局变量 int i,两个线程同时对它赋值,线程A给他赋值1,线程B给它赋值-1。那么不管两个线程怎么工作,i的值只能是1或则-1.线程A和线程B之间没有干扰。 8 | 9 | 对于 `32` 位系统的来说, `long` 类型数据和 `double` 类型数据(对于基本数据类型, `byte` , `short` , `int` , `float` , `boolean` , `char` 读写是原子操作),它们的读写并非原子性的 10 | 11 | ## 指令重排 12 | 13 | > 计算机在执行程序时,为了提高性能,编译器和处理器的常常会对指令做重排。指令重排可以保证串行语义一致,但是没有义务保证多线程间的语义也一致。 14 | 15 | - 编译器优化的重排 16 | 17 | > 编译器重排 18 | 19 | 编译器在不改变单线程程序语义的前提下,可以重新安排语句的执行顺序。 20 | 21 | - 指令并行的重排 22 | 23 | > 处理器重排 24 | 25 | 现代处理器采用了指令级并行技术来将多条指令重叠执行。如果不存在数据依赖性(即后一个执行的语句无需依赖前面执行的语句的结果),处理器可以改变语句对应的机器指令的执行顺序 26 | 27 | - 内存系统的重排 28 | 29 | > 处理器重排 30 | 31 | 由于处理器使用缓存和读写缓存冲区,这使得加载(load)和存储(store)操作看上去可能是在乱序执行,因为三级缓存的存在,导致内存与缓存的数据同步存在时间差。 32 | 33 | ## 编译器重排 34 | 35 | 有两个线程如下: 36 | 37 | ``` 38 | Thread 1 Thread2 39 | 1:r2=A; 3:r1=B; 40 | 2:B=1; 4:A=2; 41 | ``` 42 | 43 | 上述两个线程同时执行,分别有1、2、3、4四段执行代码,其中1、2属于线程1 , 3、4属于线程2 ,从程序的执行顺序上看,似乎不太可能出现r1 = 1 和r2 = 2 的情况,但实际上这种情况是有可能发现的,因为如果编译器对这段程序代码执行重排优化后,可能出现下列情况: 44 | 45 | ``` 46 | Thread 1 Thread2 47 | 2:B=1; 4:A=2; 48 | 1:r2=A; 3:r1=B; 49 | ``` 50 | 51 | 这种执行顺序下就有可能出现 `r1 = 1` 和 `r2 = 2` 的情况,这也就说明在 `多线程环境下,由于编译器优化重排的存在,两个线程中使用的变量能否保证一致性是无法确定的`。 52 | 53 | ## 处理器重排 54 | 55 | 处理器指令重排是对CPU的性能优化,从指令的执行角度来说一条指令可以分为多个步骤完成,如下: 56 | 57 | - 取指 IF 58 | - 译码和取寄存器操作数 ID 59 | - 执行或者有效地址计算 EX 60 | - 存储器访问 MEM 61 | - 写回 WB 62 | 63 | ## 可见性(Visibility) 64 | 65 | > 可见性是指当一个线程修改了某一个共享变量的值,其他线程是否能够立即知道这个修改。 66 | 67 | 对于串行程序来说,可见性问题是不存在的。因为你在任何一个操作中修改了某个变量,下个操作读取这个变量的值,一定是修改后的新值。 68 | 69 | 但在多线程环境中可就不一定了,由于线程对共享变量的操作都是线程拷贝到各自的工作内存进行操作后才写回到主内存中的,这就可能存在一个线程A修改了共享变量x的值,还未写回主内存时,另外一个线程B又对主内存中同一个共享变量x进行操作,但此时A线程工作内存中共享变量x对线程B来说并不可见,这种工作内存与主内存同步延迟现象就造成了可见性问题。 70 | 71 | 指令重排以及编译器优化也可能导致可见性问题。 72 | 73 | ![mark](http://of0qa2hzs.bkt.clouddn.com/blog/180314/899egG5ajG.png?imageslim) 74 | 75 | ## 有序性(Ordering) 76 | 77 | > 有序性是指对于单线程的执行代码,我们总是认为代码的执行是按顺序依次执行的。 78 | 79 | 对于多线程环境,则可能出现乱序现象,因为程序编译成机器码指令后可能会出现指令重排现象,重排后的指令与原指令的顺序未必一致,要明白的是,`在Java程序中,倘若在本线程内,所有操作都视为有序行为,如果是多线程环境下,一个线程中观察另外一个线程,所有操作都是无序的`,前半句指的是单线程内保证串行语义执行的一致性,后半句则指指令重排现象和工作内存与主内存同步延迟现象。 80 | 81 | ## JMM提供的解决方案 82 | 83 | - 原子性问题 84 | 85 | 除了 `JVM` 自身提供的对基本数据类型读写操作的原子性外,对于方法级别或者代码块级别的原子性操作,可以使用 `synchronized` 关键字或者重入锁( `ReentrantLock` )` 保证程序执行的原子性 86 | 87 | - 工作内存与主内存同步延迟现象导致的可见性问题 88 | 89 | 可以使用 `synchronized` 关键字或者 `volatile` 关键字解决,它们都可以使一个线程修改后的变量立即对其他线程可见。 90 | 91 | - 对于指令重排导致的可见性问题和有序性问题 92 | 93 | 可以利用 `volatile` 关键字解决,因为 `volatile` 的另外一个作用就是禁止重排序优化。 94 | 95 | - `happens-before` 原则 96 | 97 | ## JMM中的 happens-before 原则 98 | 99 | > 虽然JVM和执行系统会对指令进行一定的重排,但是指令重排是有原则的(减少程序开发的复杂性),并非所有的指令都可以随便改变执行位置。 100 | 101 | - 程序顺序原则 102 | 103 | 即在一个线程内必须保证语义串行性,也就是说按照代码顺序执行。 104 | 105 | - 锁规则 106 | 107 | 解锁(unlock)操作必然发生在后续的同一个锁的加锁(lock)之前,也就是说,如果对于一个锁解锁后,再加锁,那么加锁的动作必须在解锁动作之后(同一个锁)。 108 | 109 | - volatile规则 110 | 111 | volatile变量的写,先发生于读,这保证了volatile变量的可见性,简单的理解就是,volatile变量在每次被线程访问时,都强迫从主内存中读该变量的值,而当该变量发生变化时,又会强迫将最新的值刷新到主内存,任何时刻,不同的线程总是能够看到该变量的最新值。 112 | 113 | - 线程启动规则 114 | 115 | 线程的start()方法先于它的每一个动作,即如果线程A在执行线程B的start方法之前修改了共享变量的值,那么当线程B执行start方法时,线程A对共享变量的修改对线程B可见 116 | 117 | - 传递性 118 | 119 | A先于B ,B先于C 那么A必然先于C 120 | 121 | - 线程终止规则 122 | 123 | 线程的所有操作先于线程的终结,Thread.join()方法的作用是等待当前执行的线程终止。假设在线程B终止之前,修改了共享变量,线程A从线程B的join方法成功返回后,线程B对共享变量的修改将对线程A可见。 124 | 125 | - 线程中断规则 126 | 127 | 对线程 interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生,可以通过Thread.interrupted()方法检测线程是否中断。 128 | 129 | - 对象终结规则 130 | 131 | 对象的构造函数执行,结束先于finalize()方法 132 | 133 | -------------------------------------------------------------------------------- /研发相关/JAVA基础/JVM/内存分析.md: -------------------------------------------------------------------------------- 1 | jmap -dump:live,format=b,file=${dump_name}.bin ${PID} 2 | 3 | 4 | jmap -dump:live,format=b,file=2021_08_02_00.bin 1 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /研发相关/JAVA基础/JVM/深入理解JVM:垃圾回收.md: -------------------------------------------------------------------------------- 1 | # 深入理解JVM:垃圾回收 2 | 3 | ## 需要回收的对象 4 | 5 | - 引用计数算法 6 | 7 | 给对象添加一个引用计数器,当对象增加一个引用时计数器加 1,引用失效时计数器减 1。引用计数为 0 的对象可被回收。 8 | 9 | 因为两个对象出现循环引用的情况下,此时引用计数器永远不为 0,导致无法对它们进行回收。所以 `JVM` 并没有选用引用计数算法来管理内存。 10 | 11 | - 可达性分析算法 12 | 13 | 算法:通过一系列被称为GC Roots的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链(Reference Chain),当一个对象到GC Roots没有任何引用链相连时,证明此对象是不可用的。 14 | 15 | GC Roots对象: 16 | 17 | - 虚拟机栈中引用的对象 18 | - 方法区中类静态属性引用的对象 19 | - 方法区中的常量引用的对象 20 | - 本地方法栈中引用的对象 21 | 22 | - 引用类型 23 | 24 | 无论是通过引用计算算法判断对象的引用数量,还是通过可达性分析算法判断对象的引用链是否可达,判定对象是否存活都与 `引用` 有关。 25 | 26 | `Java` 对引用的概念进行了扩充,将引用分为: 27 | 28 | - 强引用 29 | 30 | 只要强引用存在,垃圾回收器永远不会回收调掉被引用的对象。 31 | 32 | 使用 new 一个新对象的方式来创建强引用。 33 | 34 | ``` 35 | Object obj = new Object(); 36 | ``` 37 | 38 | - 软引用 39 | 40 | 用来描述一些还有用但是并非必需的对象。 41 | 42 | 在系统将要发生内存溢出异常之前,将会对这些对象列进回收范围之中进行第二次回收。如果这次回收还没有足够的内存,才会抛出溢出异常。 43 | 44 | 使用 `SoftReference` 类来实现软引用。 45 | 46 | - 弱引用 47 | 48 | 只能生存到下一次垃圾收集发生之前,当垃圾收集器工作时,无论当前内存是否足够,都会被回收。 49 | 50 | 使用 `WeakReference` 类来实现弱引用。 51 | 52 | - 虚引用 53 | 54 | 一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用取得一个对象实例。为一个对象设置虚引用关联的唯一目的就是能在这个对象被收集器回收时收到一个系统通知。 55 | 56 | 使用 `PhantomReference` 来实现虚引用。 57 | 58 | - 回收方法区 59 | 60 | 永久代的垃圾收集效率非常低。 61 | 62 | 主要是对常量池的回收和对类的卸载。 63 | 64 | 类的卸载条件很多,需要满足以下三个条件,并且满足了也不一定会被卸载: 65 | 66 | - 该类所有的实例都已经被回收,也就是 Java 堆中不存在该类的任何实例。 67 | - 加载该类的 ClassLoader 已经被回收。 68 | - 该类对应的 java.lang.Class 对象没有在任何地方被引用,也就无法在任何地方通过反射访问该类方法。 69 | 70 | 71 | ## 垃圾收集算法 72 | 73 | - 标记-清除算法 74 | 75 | ![mark](http://of0qa2hzs.bkt.clouddn.com/blog/180408/H3aaA062D4.png?imageslim) 76 | 77 | > 对需要回收的对象进行标记,然后直接清除。是最基础的收集算法,后续的收集算法都是基于这种思路对其不足进行改进而得到的。 78 | 79 | 不足: 80 | 81 | - 标记和清除两个过程的 `效率都不高` 82 | 83 | - 标记清除之后会 `产生大量不连续的内存碎片`,内存碎片过多可能导致无法给大对象分配内存 84 | 85 | - 复制算法 86 | 87 | ![mark](http://of0qa2hzs.bkt.clouddn.com/blog/180408/I5kL1jHHBm.png?imageslim) 88 | 89 | > 将内存划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完,就将还存活的对象复制到另一块上面,然后把已经使用过的内存空间一次清理掉。 90 | 91 | - 好处:不用考虑内存碎片的问题,实现简单,运行高效。 92 | 93 | - 不足:只用到了内存的一半空间 94 | 95 | - 改进:现在的商业虚拟机都采用这种收集算法来回收新生代,但是并不是将内存划分为大小相等的两块,而是分为一块较大的 `Eden` 空间和两块较小的 `Survior` 空间,每次使用 `Ede`n 空间和其中一块 `Survivor`。在回收时,将 `Eden` 和 `Survivor` 中还存活着的对象一次性复制到另一块 `Survivor` 空间上,最后清理 `Eden` 和 使用过的那一块 `Survivor`。`HotSpot` 虚拟机的 `Eden` 和 `Survivor` 的大小比例默认为 8:1,保证了内存的利用率达到 `90 %`。如果每次回收有多于 `10%` 的对象存活,那么一块 `Survivor` 空间就不够用了,此时需要依赖于老年代进行分配担保,也就是借用老年代的空间。 96 | 97 | - 标记-整理算法 98 | 99 | ![mark](http://of0qa2hzs.bkt.clouddn.com/blog/180408/EJGaE5Addh.png?imageslim) 100 | 101 | > 让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存。 102 | 103 | - 原因:复制收集算法在对象存活率较高时就要进行比较多的复制操作,效率将会变低。更关键的是,如果不想浪费50%的空间,就需要有额外的空间进行分配担保,所以老年代一般不能直接选用这种算法。 104 | 105 | - 分代收集算法 106 | 107 | > 现在的商业虚拟机采用分代收集算法,它根据对象存活周期将内存划分为几块,不同块采用适当的收集算法。 108 | 109 | 一般将 Java 堆分为新生代和老年代。 110 | 111 | - 新生代使用:复制算法(新生代中,每次垃圾收集时都会发现大批对象死去,只有少量存活) 112 | 113 | - 老年代使用:标记 - 清理 或者 标记 - 整理 算法(老年代对象存活率高并且缺乏分配担保) 114 | 115 | ## 垃圾收集器 116 | 117 | ![mark](http://of0qa2hzs.bkt.clouddn.com/blog/180408/f30FaD4E9a.png?imageslim) 118 | 119 | 图为 `HotSpot` 虚拟机中的 7 个垃圾收集器,连线表示垃圾收集器可以配合使用 120 | 121 | - Serial 收集器 122 | 123 | - ParNew 收集器 124 | 125 | - Parallel Scavenge 收集器 126 | 127 | - Serial Old 收集器 128 | 129 | - Parallel Old 收集器 130 | 131 | - CMS 收集器 132 | 133 | - G1 收集器 134 | 135 | ## 内存分配与回收策略 136 | 137 | > 对象的内存分配,大方向上讲就是在堆上分配。对象主要分配在新生代的 Eden 区上,少数情况下也可能直接分配在老年代中。 138 | 139 | - 优先在 Eden 分配 140 | 141 | - 大对象直接进入老年代 142 | 143 | - 长期存活的对象进入老年代 144 | 145 | - 动态对象年龄判定 146 | 147 | - 空间分配担保 -------------------------------------------------------------------------------- /研发相关/JAVA基础/JVM/深入理解JVM:类加载机制.md: -------------------------------------------------------------------------------- 1 | # 深入理解JVM:类加载机制 2 | 3 | > 类是在运行期间动态加载的。 4 | 5 | ## 类加载的时机 6 | 7 | - 类的生命周期 8 | 9 | ![mark](http://of0qa2hzs.bkt.clouddn.com/blog/180409/ghcCBcKGIg.png?imageslim) 10 | 11 | - 类初始化时机 12 | 13 | 虚拟机规范中并没有强制约束何时进行加载,但是规范严格规定了有且只有下列五种情况必须对类进行初始化(加载、验证、准备都会随着发生): 14 | 15 | - 遇到 new、getstatic、putstatic、invokestatic 这四条字节码指令时,如果类没有进行过初始化,则必须先触发其初始化。最常见的生成这 4 条指令的场景是:使用 new 关键字实例化对象的时候;读取或设置一个类的静态字段(被 final 修饰、已在编译期把结果放入常量池的静态字段除外)的时候;以及调用一个类的静态方法的时候。 16 | 17 | - 使用 java.lang.reflect 包的方法对类进行反射调用的时候,如果类没有进行初始化,则需要先触发其初始化。 18 | 19 | - 当初始化一个类的时候,如果发现其父类还没有进行过初始化,则需要先触发其父类的初始化。 20 | 21 | - 当虚拟机启动时,用户需要指定一个要执行的主类(包含 main() 方法的那个类),虚拟机会先初始化这个主类; 22 | 23 | - 当使用 JDK.7 的动态语言支持时,如果一个 java.lang.invoke.MethodHandle 实例最后的解析结果为 REF_getStatic, REF_putStatic, REF_invokeStatic 的方法句柄,并且这个方法句柄所对应的类没有进行过初始化,则需要先触发其初始化; -------------------------------------------------------------------------------- /研发相关/JAVA基础/多线程/volatile关键字的作用和原理.md: -------------------------------------------------------------------------------- 1 | # volatile关键字的作用和原理 2 | 3 | ## 关键字作用 4 | 5 | - volatile 保证可见性 6 | 7 | 一旦一个共享变量(类的成员变量、类的静态成员变量)被volatile修饰之后,那么就具备了两层语义: 8 | 9 | - 保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。 10 | 11 | - 禁止进行指令重排序。 12 | 13 | 14 | - volatile 不能确保原子性 15 | 16 | ``` 17 | public class VolatileVisibility { 18 | 19 | public static volatile int i =0; 20 | 21 | public static void increase(){ 22 | i++; 23 | } 24 | } 25 | ``` 26 | 读数据和操作数据是两次操作 27 | 28 | ## volatile原理 29 | 30 | > “观察加入volatile关键字和没有加入volatile关键字时所生成的汇编代码发现,加入volatile关键字时,会多出一个lock前缀指令” 31 | 32 | lock前缀指令实际上相当于一个内存屏障(也成内存栅栏),内存屏障会提供3个功能: 33 | 34 | - 它确保指令重排序时不会把其后面的指令排到内存屏障之前的位置,也不会把前面的指令排到内存屏障的后面;即在执行到内存屏障这句指令时,在它前面的操作已经全部完成; 35 | 36 | - 它会强制将对缓存的修改操作立即写入主存; 37 | 38 | - 如果是写操作,它会导致其他CPU中对应的缓存行无效。 39 | 40 | ## 应用场景 41 | 42 | `synchronized` 关键字是防止多个线程同时执行一段代码,那么就会很影响程序执行效率,而 `volatile` 关键字在某些情况下性能要优于 `synchronized` ,但是要注意 `volatile` 关键字是无法替代 `synchronized` 关键字的,因为 `volatile` 关键字无法保证操作的原子性。通常来说,使用 `volatile` 必须具备以下 `2` 个条件: 43 | 44 | - 对变量的写操作不依赖于当前值 45 | 46 | - 该变量没有包含在具有其他变量的不变式中 47 | 48 | 49 | ## 具体应用情况 50 | 51 | - 状态标记量 52 | 53 | ```java 54 | volatile boolean flag = false; 55 | 56 | while(!flag){ 57 | doSomething(); 58 | } 59 | 60 | public void setFlag() { 61 | flag = true; 62 | } 63 | ``` 64 | 65 | - double check 66 | 67 | ```java 68 | class Singleton{ 69 | private volatile static Singleton instance = null; 70 | 71 | private Singleton() { 72 | 73 | } 74 | 75 | public static Singleton getInstance() { 76 | if(instance==null) { 77 | synchronized (Singleton.class) { 78 | if(instance==null) 79 | instance = new Singleton(); 80 | } 81 | } 82 | return instance; 83 | } 84 | } 85 | ``` 86 | 87 | ## 参考 88 | 89 | - [就是要你懂Java中volatile关键字实现原理](https://www.cnblogs.com/xrq730/p/7048693.html) -------------------------------------------------------------------------------- /研发相关/JAVA基础/多线程/创建线程的三种方式及其对比.md: -------------------------------------------------------------------------------- 1 | # 创建线程的三种方式及其对比 2 | 3 | - 继承 `Thread` 类 4 | 5 | - 定义 `Thread` 类的子类,并重写该类的 `run()` 方法,该方法的方法体就是线程需要完成的任务, `run()` 方法也称为线程执行体。 6 | 7 | - 创建 `Thread` 子类的实例,也就是创建了线程对象 8 | 9 | - 启动线程,即调用线程的 `start()` 方法 10 | 11 | ``` 12 | class MyThread extends Thread { 13 | public void run() { 14 | // ... 15 | } 16 | public static void main(String[] args) { 17 | MyThread mt = new MyThread(); 18 | mt.start(); 19 | } 20 | } 21 | ``` 22 | 23 | - 实现 `Runnable` 接口 24 | 25 | - 定义 `Runnable` 接口的实现类,一样要重写 `run()` 方法,这个 `run()` 方法和 `Thread` 中的 `run()` 方法一样是线程的执行体 26 | 27 | - 创建 `Runnable` 实现类的实例,并用这个实例作为 `Thread` 的 `target` 来创建 `Thread` 对象,这个 `Thread` 对象才是真正的线程对象 28 | 29 | - 第三部依然是通过调用线程对象的 `start()` 方法来启动线程 30 | 31 | ``` 32 | public class MyRunnable implements Runnable { 33 | public void run() { 34 | // ... 35 | } 36 | public static void main(String[] args) { 37 | MyRunnable instance = new MyRunnable(); 38 | Tread thread = new Thread(instance); 39 | thread.start(); 40 | } 41 | } 42 | ``` 43 | 44 | - 实现 `Callable` 接口 45 | 46 | > 和 `Runnable` 接口不一样, `Callable` 接口提供了一个 `call()` 方法作为线程执行体, `call()` 方法比 `run()` 方法功能要强大(`有返回值和可以抛出异常`)。 47 | 48 | - 创建 `Callable` 接口的实现类,并实现 `call()` 方法,然后创建该实现类的实例(从 `java8` 开始可以直接使用 `Lambda` 表达式创建 `Callable` 对象)。 49 | 50 | - 使用 `FutureTask` 类来包装 `Callable` 对象,该 `FutureTask` 对象封装了 `Callable` 对象的 `call()` 方法的返回值 51 | 52 | - 使用 `FutureTask` 对象作为 `Thread` 对象的 `target` 创建并启动线程(因为 `FutureTask` 实现了 `Runnable` 接口) 53 | 54 | - 调用 `FutureTask` 对象的 `get()` 方法来获得子线程执行结束后的返回值 55 | 56 | ``` 57 | public class MyCallable implements Callable { 58 | public Integer call() { 59 | return 1; 60 | } 61 | public static void main(String[] args) throws ExecutionException, InterruptedException { 62 | MyCallable mc = new MyCallable(); 63 | FutureTask ft = new FutureTask<>(mc); 64 | Thread thread = new Thread(ft); 65 | thread.start(); 66 | System.out.println(ft.get()); 67 | } 68 | } 69 | ``` 70 | 71 | - 实现接口 vs 继承 Thread 72 | 73 | - 线程只是实现 `Runnable` 或实现 `Callable` 接口,还可以继承其他类。 74 | 75 | - 实现接口,多个线程可以共享一个 `target` 对象,非常适合多线程处理同一份资源的情形。 76 | 77 | - 实现接口编程稍微复杂,如果需要访问当前线程,必须调用 `Thread` . `currentThread()` 方法。 78 | 79 | - 继承 `Thread` 类的线程类不能再继承其他父类(Java` 单继承决定)。 80 | 81 | > 一般推荐采用实现接口的方式来创建多线程 -------------------------------------------------------------------------------- /研发相关/JAVA基础/多线程/同步锁,乐观锁,悲观锁.md: -------------------------------------------------------------------------------- 1 | # 同步锁,乐观锁,悲观锁 2 | 3 | ## Synchronized 4 | 5 | > 所有对象都自动含有单一的锁(监视器),当在对象上调用其任意 `synchronized` 方法的时候,此对象都被加锁。`对于某个特定对象来说,其所有synchronized方法共享同一个锁`,这可以被用来防止多个任务同时访问被编码为对象内存。 6 | 7 | - 对于同步方法,锁是当前实例对象。 8 | 9 | ``` 10 | public synchronized void test(int n); 11 | ``` 12 | 13 | > 同一时刻对于每一个类实例,其所有声明为 `synchronized` 的成员函数中至多只有一个处于可执行状态 14 | 15 | - 对于静态同步方法,锁是当前对象的 `Class` 对象。 16 | 17 | ``` 18 | public static synchronized void test(int n); 19 | ``` 20 | 21 | - 对于同步方法块,锁是 `Synchonized` 括号里配置的对象。 22 | 23 | ``` 24 | synchronized(SyncObject.Class) { 25 | //允许访问控制的代码 26 | } 27 | 28 | or 29 | 30 | synchronized(this) { 31 | //允许访问控制的代码 32 | } 33 | ``` 34 | 35 | > 代码必须获得对象 syncObject (类实例或类)的锁方能执行 36 | ## 悲观锁 37 | 38 | > 悲观,则会假设情况总是最坏的,即共同维护的数据总会被其他线程修改,所以每次取数据的时候都会上锁,避免其他人修改。 39 | 40 | - `synchronized` 关键字的实现也是悲观锁。 41 | 42 | - 悲观锁的缺点 43 | 44 | - 在多线程竞争下,加锁、释放锁会导致比较多的上下文切换和调度延时,引起性能问题。 45 | 46 | - 一个线程持有锁会导致其它所有需要此锁的线程挂起。 47 | 48 | - 如果一个优先级高的线程等待一个优先级低的线程释放锁会导致优先级倒置,引起性能风险。 49 | 50 | ## 乐观锁 51 | 52 | > 乐观,则假设情况总是最好的,即共同维护的数据不会被其他线程修改,所以不会上锁(即无锁)。 53 | 54 | - CAS(Compare and Swap) 55 | 56 | > 非阻塞性,不存在死锁问题。没有锁竞争带来的系统开销,也没有线程间频繁调度带来的开销,具有更优越的性能。 57 | 58 | - 算法过程 59 | 60 | 包含三个参数 `CVS(V,E,N)`。`V`表示要更新的变量,`E`表示预期值,`N`表示新值。仅当 `V` 值等于 `E` 值时,才会将 `V` 的值设为 `N` ,如果 `V` 值与 `E` 值不同,则说明已经有其他线程做了更新,则当前线程什么都不做。 61 | 最后, `CAS` 返回当前 `V` 的真实值。当多个线程同时使用 `CAS` 操作一个变量时,只有一个会胜出,并成功更新,其余均会失败。 62 | 失败的线程不会挂起而是允许再次尝试。 63 | 64 | > 硬件层面,大部分处理器已经支持原子化的 `CAS` 指令。 65 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /研发相关/JAVA基础/多线程/线程复用:线程池.md: -------------------------------------------------------------------------------- 1 | # 线程复用:线程池 2 | 3 | > 为了避免系统频繁地创建和销毁线程,我们可以让创建的线程进行复用。线程池中,总有几个活跃线程。当你需要使用线程时,可以从池中随便获取一个空闲线程,当工作完成时,线程不会关闭而是退回池中。 4 | 5 | - JDK 提供的线程池工厂方法 6 | 7 | - `newFixedThreadPool` 8 | 9 | 返回固定线程数量的线程池。 10 | 11 | 当有一个新的任务提交时,线程池若有空闲线程,则立即执行。若没有,则新的任务会被暂存到一个任务队列中,待线程空闲时,便处理在任务队列中的任务。 12 | 13 | - `newSingleThreadExecutor` 14 | 15 | 返回只有一个线程的线程池。 16 | 17 | 若多余一个任务被提及,则保存到任务队列中,线程空闲时按照先入先出的顺序执行任务。 18 | 19 | - `newCachedThreadPool` 20 | 21 | 返回一个可根据实际情况调整线程数量的线程池。 22 | 23 | 线程池的线程数量不确定,但若有空闲线程可以复用,则会有限使用可复用的线程。若所有线程均在工作,又有新任务提交,则创建新的线程处理任务。当所有线程在任务执行完之后,将返回线程池进行复用。 24 | 25 | - `newSingleThreadScheduledExecutor` 26 | 27 | 返回一个 `ScheduledExecutorService` 对象,线程池大小为1。 28 | 29 | `ScheduledExecutorService` 接口在 `ExecutorService` 接口之上扩展了给定时间执行某任务的功能。 30 | 31 | - `newScheduledThreadPool` 32 | 33 | 返回指定线程数量的 `ScheduledExecutorService` 对象。 34 | 35 | - 线程池的内部实现 36 | 37 | > JDK 提供的线程池工厂方法实质就是 `ThreadPoolExecutor` 的不同参数的不同实现 38 | 39 | ``` 40 | public ThreadPoolExecutor(int corePoolSize, 41 | int maximumPoolSize, 42 | long keepAliveTime, 43 | TimeUnit unit, 44 | BlockingQueue workQueue, 45 | ThreadFactory threadFactory, 46 | RejectedExecutionHandler handler) 47 | ``` 48 | `corePoolSize` : 指定线程池中的线程数量。 49 | 50 | `maximumPoolSize` :指定线程池中的最大线程数量。 51 | 52 | `keepAliveTime` :超过corePoolSize的空闲线程,在多长时间内,会被销毁。 53 | 54 | `unit` :keepAliveTime的单位。 55 | 56 | `workQueue` :提交但未执行的任务队列。 57 | 58 | - 直接提交的队列 59 | 60 | 该功能由 `SynchronousQueue` 对象提供,提交的任务不会被保存,而是直接提交给线程执行,如果没有空闲的线程,则尝试创建新的线程,如果已达到最大值则执行拒绝策略。 61 | 62 | - 有界的任务队列 63 | 64 | 由 `ArrayblockingQueue` 实现, `ArrayblockingQueue` 的构造函数需要带一个容量参数(队列最大容量)。当有新的任务时,如果线程池实际线程数小于 `corePoolSize` ,则优先创新新的线程,如果大于 `corePoolSize` ,则将任务加入等待队列。 65 | 66 | 若等待队列已满,则在总线程数不大于 `maximumPoolSize` 的前提下创建新的线程执行任务,否则则执行拒绝策略。因为需要等待队列满才会创建超出 `corePoolSize` 的线程,所以除非系统特别繁忙,否则核心线程数一般维持在 `corePoolSize` 。 67 | 68 | - 无界的任务队列 69 | 70 | 由 `LinkedBlockingQueue` 实现,和有界的任务队列类似,只是等待队列会一直扩张,如果任务的创建速度一直高于处理速度,则无界队列会一直增长,直至耗尽内存。 71 | 72 | - 优先任务队列 73 | 74 | 由 `PriorityBlockingQueue` 实现,是一个特殊的无界队列,会按照任务自身的优先级顺序先后执行。 75 | 76 | `threadFactory` :线程工厂,用于创建线程,一般默认。 77 | 78 | `handler` :拒绝策略。当任务太多时如何拒绝任务。 79 | 80 | - AbortPolicy 策略 81 | 82 | 直接抛出异常,阻止系统正常工作。 83 | 84 | - CallerRunsPolicy 策略 85 | 86 | 只要线程池未关闭,该策略直接在调用者线程中,运行当前被丢弃的任务。这样做不会真的丢弃任务,但是任务提交的线程性能可能会急剧下降。 87 | 88 | - DiscardPolicy 策略 89 | 90 | 丢弃最老的一个请求(即将被执行的下一个任务),并尝试再次提交当前任务。 91 | 92 | - DiscardOldestPolicy 策略 93 | 94 | 直接丢弃无法处理的任务,不做任何处理。 95 | 96 | - 线程池的数量 97 | 98 | Ncpu = CPU数量 99 | 100 | Ucup = CPU的使用率 101 | 102 | W/C = 等待时间与计算时间的比率 103 | 104 | 最优池: `Nthreads=Ncpu*Ncpu*(1+W/C)` -------------------------------------------------------------------------------- /研发相关/JAVA基础/多线程/锁优化策略.md: -------------------------------------------------------------------------------- 1 | # 锁优化策略 2 | 3 | ## 提升锁性能的策略 4 | 5 | - 减少锁持有时间 6 | 7 | > 只在需要锁竞争的地方加入锁的操作,减少锁的占有时间,以减少线程间互斥的可能。 8 | 9 | ``` 10 | public synchronized void test(){ 11 | code1(); 12 | mutextMethod(); 13 | code2(); 14 | } 15 | ``` 16 | 如果 `code1()` 方法和 `code2()` 方法不需要做同步控制,则会花费更多的 `cup` 时间。 17 | 18 | 优化方案:只在必要时进行同步,减少线程持有锁的时间。 19 | 20 | ``` 21 | public void test2(){ 22 | code1(); 23 | synchronized(this){ 24 | mutextMethod(); 25 | } 26 | code2(); 27 | } 28 | ``` 29 | 30 | - 减少锁粒度 31 | 32 | > 减小锁粒度也是削弱多线程竞争的有效手段。ConcurrentHashMap的实现就采用了减少锁粒度的策略,[深入理解ConcurrentHashMap](http://blog.csdn.net/myherux/article/details/79421835) 33 | 34 | 35 | - 读写分离锁 36 | 37 | > 采用锁分离的策略,将读操作与写操作分别加锁 38 | 39 | 读写锁的访问约束情况: 40 | 41 | x | 读 | 写 42 | ---|--- |--- 43 | 读 |非阻塞|阻塞 44 | 写 |阻塞 |阻塞 45 | 46 | 如果在系统中,读操作次数远远大于写操作,则读写锁就能起到很好的作用,提升系统性能。 47 | 48 | - 锁分离 49 | 50 | > 将读写锁的思想进一步延伸,就是锁分离。读写锁是根据读写操作的不同进行的锁分离,我们可以根据应用的特征,使用锁分离的思想,对独占锁进行分离。 51 | 52 | - 锁粗化 53 | 54 | 前面讲了尽量减少锁持有时间,但是如果对同一个锁不停地请求、同步和释放,本身也会消耗更多的系统资源。所以虚拟机在遇到一连串连续地对同一锁不断进行请求和释放的操作时,便会 `把所有的锁操作整合成对锁的一次请求`,从而减少对锁的请求同步次数。 55 | 56 | ``` 57 | public void test(){ 58 | synchronized(lock){ 59 | //do sth 60 | } 61 | //做其他不需要同步的工作,但是执行时间短 62 | synchronized(lock){ 63 | //continue do sth 64 | } 65 | } 66 | 67 | JVM会整合为如下形式==> 68 | 69 | public void test(){ 70 | synchronized(lock){ 71 | //do sth 72 | //做其他不需要同步的工作,但是执行时间短 73 | //continue do sth 74 | } 75 | } 76 | ``` 77 | > 实际开发中也应该衡量是否需要在合理的地方进行 `锁的粗化`。因为 `锁粗化` 实际是和 `减少锁持有时间` 相斥的思想,所以具体情况需要具体分析。 78 | 79 | ## JVM的锁优化策略 80 | 81 | - 锁偏向 82 | 83 | > 核心思想:如果一个线程获得了锁,那么锁就进入偏向模式。当这个线程再次请求锁时,无需在做任何同步操作。 84 | 85 | 对于几乎没有锁竞争的场合,偏向锁有比较好的优化效果,因为连续多次极有可能是同一个线程请求相同的锁。而对于锁竞争比较激烈的场合,其效果不佳。因为每次都是不同的线程来请求相同的锁。 86 | 87 | - 轻量级锁 88 | 89 | > 如果偏向锁失败,虚拟机并不会立刻挂起线程。它会使用一种称为轻量级锁的优化手段。 90 | 91 | 轻量级锁操作很轻便,它将对象头部作为指针,指向持有锁的线程堆栈的内部,来判断一个线程是否持有对象锁。如果线程获得轻量级锁成功,则可以顺利进入临界区。如果轻量级锁加锁失败,则表示其他线程抢到了锁,当前线程的锁请求就会膨胀为 `重量级锁`。 92 | 93 | - 自旋锁 94 | 95 | > 即使锁膨胀之后,虚拟机为什么避免线程在操作系统层面挂起,会使用 `自旋锁` 的策略。 96 | 97 | 由于当前线程暂时无法获得锁,但是什么时候可以活得锁是一个未知数。也许在几个CPU时钟周期后,就可以得到锁。于是,系统进行了一个赌注:假设在不久的将来,线程可以得到这把锁。因此,`虚拟机会让当前线程做几个空循环`,在若干次循环后,如果可以得到锁,那么顺利进入临界区。如果还不能获得锁,才会真正在系统层面挂起。 98 | 99 | > 自旋锁的好处是减少线程上下文切换的消耗,缺点是循环会消耗CPU。 100 | 101 | - 锁消除 102 | 103 | > JVM在JIT编译时,会通过对运行上下文的扫描,去除不可能存在共享资源竞争的锁。 104 | 105 | ``` 106 | public String[] createStrings(){ 107 | Vector v=new Vector(); 108 | for(int i=0;i<100;i++){ 109 | v.add(Integer.toString(i)); 110 | } 111 | return v.toArray(new String[]{}); 112 | } 113 | ``` 114 | 115 | 变量 `v` 只在 `createString()` 函数中使用,因此,它只是一个局部变量。局部变量是在线程栈上分布的,属于线程私有的数据,因此不可能被其他线程访问。所以,Vevtor内部的所有锁同步都是没有必要的,虚拟机会将这些所操作去除。 116 | 117 | 118 | ## 参考 119 | 120 | - 书 《实战Java高并发程序设计》 121 | 122 | - [死磕Java并发:深入分析synchronized的实现原理](http://developer.51cto.com/art/201702/532564.htm) -------------------------------------------------------------------------------- /研发相关/JAVA基础/集合/深入分析LinkedHashMap.md: -------------------------------------------------------------------------------- 1 | # 深入分析LinkedHashMap (JDK1.8) 2 | 3 | - 类名和继承关系 4 | 5 | ``` 6 | public class LinkedHashMap 7 | extends HashMap 8 | implements Map 9 | { 10 | ``` 11 | 12 | - 内部存储结构 13 | 14 | ``` 15 | /** 16 | * HashMap.Node subclass for normal LinkedHashMap entries. 17 | */ 18 | static class Entry extends HashMap.Node { 19 | Entry before, after; 20 | Entry(int hash, K key, V value, Node next) { 21 | super(hash, key, value, next); 22 | } 23 | } 24 | 25 | /** 26 | * 用于指向双向链表的头部 27 | */ 28 | transient LinkedHashMap.Entry head; 29 | 30 | /** 31 | * 用于指向双向链表的尾部 32 | */ 33 | transient LinkedHashMap.Entry tail; 34 | ``` 35 | 36 | `LinkedHashMap` 实际是在 `HashMap` 的 `Node` 结构里面加上双向链表 37 | 38 | ![mark](http://of0qa2hzs.bkt.clouddn.com/blog/180313/AJh94baEg9.png?imageslim) 39 | 40 | 在 `LinkedHashMapMap` 中,所有 `put` 进来的 `Entry` 都保存在 `HashMap` 中,但由于它又额外定义了一个以 `head` 为头结点的空的双向链表,因此对于每次 `put` 进来 `Entry` 还会将其插入到双向链表的尾部。 41 | 42 | ![mark](http://of0qa2hzs.bkt.clouddn.com/blog/180313/2aa6jef7ac.png?imageslim) 43 | 44 | > 由上图,除去红色双向箭头就是一个 `HashMap` ,只看红色双向箭头则是一个双向链表。所以, `LinkedHashMap` 就是一个标准的 `HashMap+LinkedList` 。 45 | 46 | - LinkedHashMap的构造方法 47 | 48 | ``` 49 | public LinkedHashMap(int initialCapacity, float loadFactor) { 50 | super(initialCapacity, loadFactor); 51 | accessOrder = false; 52 | } 53 | ``` 54 | 55 | > 比 `HashMap` 多了一个 `accessOrder` 的参数,用来指定按照 `LRU` 排列方式还是顺序插入的排序方式 56 | 57 | - get()方法 58 | 59 | ``` 60 | public V get(Object key) { 61 | Node e; 62 | //调用HashMap的getNode的方法 63 | if ((e = getNode(hash(key), key)) == null) 64 | return null; 65 | //在取值后对参数accessOrder进行判断,如果为true,执行afterNodeAccess 66 | if (accessOrder) 67 | afterNodeAccess(e); 68 | return e.value; 69 | } 70 | ``` 71 | > accessOrder为true则表示按照基于访问的顺序来排列 72 | 73 | ``` 74 | //将最近使用的Node,放在链表的最末尾 75 | void afterNodeAccess(Node e) { // move node to last 76 | LinkedHashMap.Entry last; 77 | //仅当按照LRU原则且e不在最末尾,才执行修改链表,将e移到链表最末尾的操作 78 | if (accessOrder && (last = tail) != e) { 79 | //将e赋值临时节点p, b是e的前一个节点, a是e的后一个节点 80 | LinkedHashMap.Entry p = 81 | (LinkedHashMap.Entry)e, b = p.before, a = p.after; 82 | //节点移动 83 | p.after = null; 84 | if (b == null) 85 | head = a; 86 | else 87 | b.after = a; 88 | if (a != null) 89 | a.before = b; 90 | else 91 | last = b; 92 | if (last == null) 93 | head = p; 94 | else { 95 | p.before = last; 96 | last.after = p; 97 | } 98 | tail = p; 99 | ++modCount; 100 | } 101 | } 102 | ``` 103 | 104 | - put()方法 105 | 106 | > `LinkedHashMap` 没有重写 `put` 方法,调用的都是 `HashMap` 的 `put` 方法。为什么它也会执行 `afterNodeAccess()` 方法呢,因为这个方法 `HashMap` 就是存在的,但是没有实现, `LinkedHashMap` 重写了 `afterNodeAccess()` 这个方法。 -------------------------------------------------------------------------------- /研发相关/K8s/core/一些命令.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | - 查看集群在哪个节点 4 | 5 | kubectl get pod -n [namespace] -o wide 6 | 7 | - 找到集群在的节点可以直接上节点服务器查看docker运行情况 8 | 9 | docker stats 10 | 11 | - 查看pod的信息详情,帮助分析问题 12 | 13 | kubectl describe po/[pod_name] -n [namespace] 14 | 15 | - 查看集群的CPU和内存情况 16 | 17 | kubectl top pod -n [namespace] -------------------------------------------------------------------------------- /研发相关/README.md: -------------------------------------------------------------------------------- 1 | ## 研发相关 2 | 3 | 工作中的记录与总结,研发相关的一切~ 4 | 5 | - [CAS](./CAS/README.md) 6 | 7 | - [分布式](./分布式/README.md) 8 | 9 | - [负载均衡](./负载均衡/README.md) 10 | 11 | - [缓存](./缓存/README.md) 12 | 13 | - [区块链](./区块链/README.md) 14 | 15 | - [日志分析](./日志分析/README.md) 16 | 17 | - [微服务](./微服务/README.md) 18 | 19 | - [API管理](./API管理/README.md) 20 | 21 | - [CI/CD](./CI-CD/README.md) 22 | 23 | - [IDE](./IDE/README.md) 24 | 25 | - [Kafka](./Kafka/README.md) 26 | 27 | - [Springboot](./Springboot/README.md) 28 | -------------------------------------------------------------------------------- /研发相关/Spring/Springboot/README.md: -------------------------------------------------------------------------------- 1 | ## Springboot 2 | 3 | - [使用Swagger来进行前后端协作](./使用Swagger来进行前后端协作.md) 4 | 5 | - [HikariCP配置详解+多数据源](./HikariCP配置详解+多数据源.md) 6 | 7 | - [Spring-cloud-feign添加统一的Header](./Spring-cloud-feign添加统一的Header) 8 | 9 | - [Springboot-2.x-脚手架项目](./Springboot-2.x-脚手架项目) 10 | 11 | - [SpringBoot2.x-定时任务](./SpringBoot2.x-定时任务) 12 | 13 | - [SpringBoot2.x-Actutor-micrometer-自定义Mertics](./SpringBoot2.x-Actutor-micrometer-自定义Mertics.md) 14 | 15 | - [SpringBoot2.x中的应用监控:Actuator+InFluxDB+Telegraf+Grafana](./SpringBoot2.x中的应用监控:Actuator+InFluxDB+Telegraf+Grafana.md) 16 | 17 | - [SpringBoot2.x中的应用监控:Actuator+Prometheus+Grafana](./SpringBoot2.x中的应用监控:Actuator+Prometheus+Grafana.md) 18 | 19 | - [SpringBoot2.x中使用Actuator来做应用监控](./SpringBoot2.x中使用Actuator来做应用监控.md) 20 | 21 | - [SpringBoot获取配置文件属性](./SpringBoot获取配置文件属性.md) -------------------------------------------------------------------------------- /研发相关/Spring/Springboot/Spring-cloud-feign添加统一的Header.md: -------------------------------------------------------------------------------- 1 | # Spring-cloud-feign添加统一的Header 2 | 3 | ## Overview 4 | 5 | - [Spring Cloud OpenFeign Doc](https://cloud.spring.io/spring-cloud-openfeign/reference/html/) 6 | - [Github](https://github.com/OpenFeign/feign) 7 | 8 | ## 业务需求 9 | 10 | 请求某些机密服务,需要做加密操作,所以需要添加统一的签名 `Header` 。 11 | 12 | ## Code 13 | 14 | ``` 15 | @Slf4j 16 | @Component 17 | public class testFeignInterceptor implements RequestInterceptor { 18 | 19 | public void apply(RequestTemplate requestTemplate) { 20 | String url = requestTemplate.url(); 21 | // 只对特定url进行签名添加Header的操作 22 | if (!url.startsWith("/test/api")) { 23 | return; 24 | } 25 | String accessKeyId = "ak"; 26 | String privateKey = "MI"; 27 | 28 | String body = ""; 29 | if (requestTemplate.body() != null) { 30 | body = new String(requestTemplate.body()); 31 | } 32 | String contentSHA256 = getContentSHA256(body); 33 | String date = new Date().toString(); 34 | String nonce = UUID.randomUUID().toString().replace("-", ""); 35 | String signature = getSignature(privateKey, requestTemplate.url(), requestTemplate.method(), contentSHA256, date, nonce); 36 | 37 | requestTemplate.header("Accept", "application/json"); 38 | requestTemplate.header("Content-SHA256", contentSHA256); 39 | requestTemplate.header("Content-Type", "application/json"); 40 | requestTemplate.header("Date", date); 41 | requestTemplate.header("x-signature-nonce", nonce); 42 | requestTemplate.header("Authorization", accessKeyId + ":" + signature); 43 | } 44 | } 45 | ``` 46 | 47 | ## 其他 48 | 49 | 过程中,需要打印请求和响应的详细信息 50 | 51 | 添加 `FeignConfiguration`: 52 | ``` 53 | @Configuration 54 | public class FeignConfiguration { 55 | public static int connectTimeOutMillis = 30000; 56 | public static int readTimeOutMillis = 30000; 57 | 58 | @Bean 59 | public Request.Options options() { 60 | return new Request.Options(connectTimeOutMillis, readTimeOutMillis); 61 | } 62 | 63 | @Bean 64 | Logger.Level feignLoggerLevel() { 65 | //这里记录所有,根据实际情况选择合适的日志level 66 | return Logger.Level.FULL; 67 | } 68 | } 69 | ``` 70 | 71 | 添加配置: 72 | ``` 73 | logging: 74 | level: 75 | com: 76 | test: 77 | client: debug 78 | ``` -------------------------------------------------------------------------------- /研发相关/Spring/Springboot/SpringBoot2.x-Actutor-micrometer-自定义Mertics.md: -------------------------------------------------------------------------------- 1 | # SpringBoot2.x-Actutor-micrometer-自定义Mertics 2 | 3 | ## 前言 4 | 5 | - [SpringBoot2.x使用Actuator来做应用监控](https://blog.csdn.net/MyHerux/article/details/80670557) 6 | 7 | ## 示例 8 | 9 | - 注册 Metrics 10 | 11 | 实现 `MeterBinder` 接口的 `bindTo` 方法,将要采集的指标注册到 `MeterRegistry` 12 | 13 | ``` 14 | @Component 15 | public class JobMetrics implements MeterBinder { 16 | 17 | 18 | public Counter job1Counter; 19 | 20 | public Counter job2Counter; 21 | 22 | public Map map; 23 | 24 | JobMetrics() { 25 | map = new HashMap<>(); 26 | } 27 | 28 | @Override 29 | public void bindTo(MeterRegistry meterRegistry) { 30 | this.job1Counter = Counter.builder("my_job") 31 | .tags(new String[]{"name", "job1"}) 32 | .description("Job 1 execute count").register(meterRegistry); 33 | 34 | this.job2Counter = Counter.builder("my_job") 35 | .tags(new String[]{"name", "job2"}) 36 | .description("Job 2 execute count").register(meterRegistry); 37 | 38 | Gauge.builder("my_job_gauge", map, x -> x.get("x")) 39 | .tags("name", "job1") 40 | .description("") 41 | .register(meterRegistry); 42 | 43 | } 44 | 45 | } 46 | 47 | ``` 48 | 49 | - 更新 Metrics 50 | 51 | 示例采集了 `job` 执行的一些信息。 52 | 53 | ``` 54 | @Slf4j 55 | @Component 56 | public class MyJob { 57 | 58 | private Integer count1 = 0; 59 | 60 | private Integer count2 = 0; 61 | 62 | @Autowired 63 | private JobMetrics jobMetrics; 64 | 65 | @Async("main") 66 | @Scheduled(fixedDelay = 1000) 67 | public void doSomething() { 68 | count1++; 69 | jobMetrics.job1Counter.increment(); 70 | jobMetrics.map.put("x", Double.valueOf(count1)); 71 | System.out.println("task1 count:" + count1); 72 | } 73 | 74 | @Async 75 | @Scheduled(fixedDelay = 10000) 76 | public void doSomethingOther() { 77 | count2++; 78 | jobMetrics.job2Counter.increment(); 79 | System.out.println("task2 count:" + count2); 80 | } 81 | } 82 | ``` 83 | 84 | ## 自定义 Metrics 指标 85 | 86 | - `Counter:` 只增不减的计数器 87 | 88 | 计数器可以用于记录只会增加不会减少的指标类型,比如记录应用请求的总量(http_requests_total)。 89 | 90 | ``` 91 | this.job1Counter = Counter.builder("my_job") //指定指标的名称 92 | .tags(new String[]{"name", "job1"}) // 指定相同指标的不同tag 93 | .description("Job 1 execute count").register(meterRegistry); 94 | ``` 95 | 96 | ``` 97 | Counter.increment() // 每次增加1 98 | Counter.increment(5D) // 每次增加指定数目 99 | ``` 100 | 101 | 访问 `http://localhost:8080//actuator/metrics/` 可以看到新增了我们添加的指标名称 `my_job` 102 | 103 | ![](http://cdn.heroxu.com/20180817153449776998953.png) 104 | 105 | 进入下一级目录:`http://localhost:8080//actuator/metrics/my_job` ,可以看到总的执行次数是 `602` ,有两种不同的 `tag` 106 | 107 | ``` 108 | {"name":"my_job","measurements":[{"statistic":"COUNT","value":602.0}],"availableTags":[{"tag":"name","values":["job2","job1"]}]} 109 | ``` 110 | 111 | 查看 `tag:job1` 的详情:`http://localhost:8080//actuator/metrics/my_job?tag=name:job1` 112 | 113 | ``` 114 | {"name":"my_job","measurements":[{"statistic":"COUNT","value":130.0}],"availableTags":[]} 115 | ``` 116 | 117 | - `Gauge:` 可增可减的仪表盘 118 | 119 | 对于这类可增可减的指标,可以用于反应应用的 `当前状态`,比如主机当前空闲的内存大小(node_memory_MemFree) 120 | 121 | ``` 122 | Gauge.builder("my_job_gauge", map, x -> x.get("x")) 123 | .tags("name", "job1") 124 | .description("") 125 | .register(meterRegistry); 126 | ``` 127 | 128 | ``` 129 | jobMetrics.map.put("x", Double.valueOf(count1)); 130 | ``` 131 | 132 | ## 项目地址 133 | 134 | [spring-boot-2.x-scaffold](https://github.com/MyHerux/spring-boot-2.x-scaffold) -------------------------------------------------------------------------------- /研发相关/Spring/Springboot/SpringBoot2.x中使用Actuator来做应用监控.md: -------------------------------------------------------------------------------- 1 | # SpringBoot 2.x 中使用 Actuator 来做应用监控 2 | 3 | ## Actuator 4 | 5 | [Spring-boot-actuator](https://github.com/spring-projects/spring-boot/tree/v2.0.2.RELEASE/spring-boot-project/spring-boot-actuator) module 可帮助您在将应用程序投入生产时监视和管理应用程序。您可以选择使用 HTTP 端点或 JMX 来管理和监控您的应用程序。Auditing, health, and metrics gathering 也可以自动应用于您的应用程序。 6 | 7 | - 添加依赖,开启监控 8 | 9 | ``` 10 | 11 | 12 | org.springframework.boot 13 | spring-boot-starter-actuator 14 | 15 | 16 | ``` 17 | - 特性 18 | 19 | - Endpoints 20 | 21 | `Actuator endpoints` 允许你去监控和操作你的应用。SpringBoot包含了许多内置的端点,当然你也可以添加自己的端点。比如 `health` 端点就提供了基本的应用健康信息。 22 | 23 | - Metrics 24 | 25 | `Spring Boot Actuator` 提供 `dimensional metrics` 通过集成 [Micrometer](https://micrometer.io/). 26 | 27 | - Audit 28 | 29 | `Spring Boot Actuator` 有一套灵活的审计框架会发布事件到 `AuditEventRepository`。 30 | 31 | - 2.0 更新 32 | 33 | - 基础路径更新 34 | 35 | 基础路径由 `/` 调整到 `/actuator` 下 36 | 37 | - 启动端点 endpoint 38 | 39 | 默认只启动了 `health` 和 `info` 端点,可以通过 `application.yml` 配置修改: 40 | 41 | ``` 42 | management: 43 | endpoints: 44 | web: 45 | exposure: 46 | include: health,info,env,metrics 47 | ``` 48 | 49 | 项目启动时可以看到暴露出来的接口信息: 50 | 51 | ![](http://cdn.heroxu.com/2018060715283596955543.png) 52 | 53 | - 主要的端点 54 | 55 | HTTP方法| 路径| 描述| 鉴权 56 | - | :-|:-|-: 57 | GET |/autoconfig| 查看自动配置的使用情况| true 58 | GET |/configprops| 查看配置属性,包括默认配置| true 59 | GET |/beans| 查看bean及其关系列表 |true 60 | GET |/dump| 打印线程栈 |true 61 | GET |/env |查看所有环境变量 |true 62 | GET |/env/{name}| 查看具体变量值 |true 63 | GET |/health |查看应用健康指标 |false 64 | GET |/info |查看应用信息 |false 65 | GET |/mappings| 查看所有url映射 |true 66 | GET |/metrics |查看应用基本指标| true 67 | GET |/metrics/{name}| 查看具体指标| true 68 | POST | /shutdown |关闭应用 |true 69 | GET |/trace |查看基本追踪信息 |true 70 | 71 | - 通过web访问暴露的端点 72 | 73 | - http://localhost:8077/actuator/metrics 74 | 75 | ``` 76 | {"names":["jvm.memory.max","process.files.max","jvm.gc.memory.promoted","tomcat.cache.hit","system.load.average.1m","tomcat.cache.access","jvm.memory.used","jvm.gc.max.data.size","jvm.memory.committed","system.cpu.count","logback.events","tomcat.global.sent","jvm.buffer.memory.used","tomcat.sessions.created","jvm.threads.daemon","system.cpu.usage","jvm.gc.memory.allocated","tomcat.global.request.max","tomcat.global.request","tomcat.sessions.expired","jvm.threads.live","jvm.threads.peak","tomcat.global.received","process.uptime","tomcat.sessions.rejected","process.cpu.usage","tomcat.threads.config.max","jvm.classes.loaded","jvm.gc.pause","jvm.classes.unloaded","tomcat.global.error","tomcat.sessions.active.current","tomcat.sessions.alive.max","jvm.gc.live.data.size","tomcat.servlet.request.max","tomcat.threads.current","tomcat.servlet.request","process.files.open","jvm.buffer.count","jvm.buffer.total.capacity","tomcat.sessions.active.max","tomcat.threads.busy","process.start.time","tomcat.servlet.error"]} 77 | ``` 78 | 79 | - http://localhost:8077/actuator/metrics/jvm.memory.max 80 | 81 | ``` 82 | {"name":"jvm.memory.max","measurements":[{"statistic":"VALUE","value":3.455057919E9}],"availableTags":[{"tag":"area","values":["heap","nonheap"]},{"tag":"id","values":["Compressed Class Space","PS Survivor Space","PS Old Gen","Metaspace","PS Eden Space","Code Cache"]}]} 83 | ``` 84 | 85 | ## Micrometer 86 | 87 | `Springboot2` 在 `spring-boot-actuator` 中引入了 `micrometer` ,对 `1.x` 的 `metrics` 进行了重构,另外支持对接的监控系统也更加丰富( `Atlas、Datadog、Ganglia、Graphite、Influx、JMX、NewRelic、Prometheus、SignalFx、StatsD、Wavefront` )。 88 | 89 | ### Prometheus 90 | 91 | [SpringBoot2.x中的应用监控:Actuator+Prometheus+Grafana](https://blog.csdn.net/myherux/article/details/80667524) 92 | 93 | ### Influx 94 | 95 | - 添加依赖 96 | 97 | ``` 98 | 99 | io.micrometer 100 | micrometer-registry-influx 101 | 102 | ``` 103 | 104 | -------------------------------------------------------------------------------- /研发相关/Spring/Springboot/SpringBoot2.x中的应用监控:Actuator+Prometheus+Grafana.md: -------------------------------------------------------------------------------- 1 | # SpringBoot2.x中的应用监控:Actuator+Prometheus+Grafana 2 | 3 | ## 总览 4 | 5 | `Actuator` 提供端点将数据暴露出来, `Prometheus` 定时去拉取数据并保存和提供搜索和展示, `Grafana` 提供更加精美的图像化展示 6 | 7 | ## Actuator 8 | 9 | [SpringBoot2.x使用Actuator来做应用监控](https://blog.csdn.net/myherux/article/details/80670557) 10 | 11 | ## Prometheus 12 | 13 | [Prometheus](https://github.com/prometheus/prometheus) 是 [Cloud Native Computing Foundation](https://www.cncf.io/) 项目之一,是一个系统和服务监控系统。它按给定的时间间隔从配置的目标收集指标,评估规则表达式,显示结果,并且如果观察到某些条件为真,则可触发警报。 14 | 15 | ### 特性 16 | 17 | - `多维度` 数据模型(由度量名称和键/值维度集定义的时间序列) 18 | 19 | - `灵活的查询语言` 来利用这种维度 20 | 21 | - 不依赖分布式存储;`单个服务器节点是自治的` 22 | 23 | - 时间序列采集通过HTTP上的 `pull model` 发生 24 | 25 | - `推送时间序列` 通过中间网关得到支持 26 | 27 | - 通过 `服务发现` 或 `静态配置` 来发现目标 28 | 29 | - 多种模式的 `图形和仪表盘支持` 30 | 31 | - 支持分级和水平 `federation` 32 | 33 | ### 架构图 34 | 35 | ![](http://cdn.heroxu.com/20180611152870866231216.png) 36 | 37 | ### 集成到应用 38 | 39 | - 添加依赖 40 | 41 | ``` 42 | 43 | io.micrometer 44 | micrometer-registry-prometheus 45 | 46 | ``` 47 | 48 | - 启动端点 49 | 50 | 启用 `/actuator/prometheus` 端点,供 `Prometheus` 来抓取指标。在启动的端点中,添加 prometheus。 51 | 52 | ``` 53 | management: 54 | endpoints: 55 | web: 56 | exposure: 57 | include: health,info,env,metrics,prometheus 58 | ``` 59 | 60 | - 启动 SpringBoot 服务 61 | 62 | 部署自己的 SpringBoot 项目,查看 `/actuator/prometheus`: 63 | 64 | ``` 65 | # HELP jvm_gc_max_data_size_bytes Max size of old generation memory pool 66 | # TYPE jvm_gc_max_data_size_bytes gauge 67 | jvm_gc_max_data_size_bytes 1.395654656E9 68 | ... 69 | ``` 70 | 71 | - 通过 Prometheus 来抓取数据 72 | 73 | `Prometheus` 会按照配置的时间周期去 `pull` 暴露的端点(`/actuator/prometheus`)中的指标数据 74 | 75 | - prometheus.yml 配置 76 | 77 | 参考 [`官方的配置`](https://github.com/prometheus/prometheus/blob/master/documentation/examples/prometheus.yml) 78 | 79 | 我的配置(SpringBoot项目是部署在8077端口的): 80 | 81 | ``` 82 | # my global config 83 | global: 84 | scrape_interval: 15s # Set the scrape interval to every 15 seconds. Default is every 1 minute. 85 | evaluation_interval: 15s # Evaluate rules every 15 seconds. The default is every 1 minute. 86 | # scrape_timeout is set to the global default (10s). 87 | 88 | # Alert manager configuration 89 | alerting: 90 | alertmanagers: 91 | - static_configs: 92 | - targets: 93 | # - alertmanager:9093 94 | 95 | # Load rules once and periodically evaluate them according to the global 'evaluation_interval'. 96 | rule_files: 97 | # - "first_rules.yml" 98 | # - "second_rules.yml" 99 | 100 | 101 | scrape_configs: 102 | - job_name: 'prometheus' 103 | static_configs: 104 | - targets: ['localhost:9090'] 105 | - job_name: 'spring' 106 | metrics_path: '/actuator/prometheus' 107 | static_configs: 108 | - targets: ['localhost:8077'] 109 | ``` 110 | 111 | - 启动 prometheus docker 112 | 113 | 指定刚才的 `prometheus.yml` 配置地址 `/opt/demo/prometheus.yml` ,创建镜像 114 | 115 | ``` 116 | docker run -p 9090:9090 -v /opt/demo/prometheus.yml:/etc/prometheus/prometheus.yml \ 117 | prom/prometheus 118 | ``` 119 | 120 | - 访问 9090 端口 121 | 122 | ![](http://cdn.heroxu.com/20180612152879174729237.png) 123 | 124 | ## Grafana 125 | 126 | The open platform for beautiful 127 | analytics and monitoring. 128 | 129 | - 安装 130 | 131 | - 官网下载 132 | 133 | > https://grafana.com/ 134 | 135 | - Mac 安装:brew install grafana 136 | 137 | - 启动本地服务 138 | 139 | > brew services start grafana 140 | 141 | 访问 `http://127.0.0.1:3000/`(默认账号密码是 `admin/admin` ): 142 | 143 | ![](http://cdn.heroxu.com/20180612152880672344532.png) 144 | 145 | - 配置 Prometheus 数据源 146 | 147 | URL填入 Prometheus 服务的地址: 148 | 149 | ![](http://cdn.heroxu.com/20180612152880725293179.png) 150 | 151 | - 添加 Dashboards 152 | 153 | ![](http://cdn.heroxu.com/2018061215288088143797.png) 154 | 155 | - 查看监控页面 156 | 157 | ![](http://cdn.heroxu.com/20180612152880910476496.png) 158 | 159 | -------------------------------------------------------------------------------- /研发相关/Spring/Springboot/SpringBoot2.x中的应用监控:Actuator+InFluxDB+Telegraf+Grafana.md: -------------------------------------------------------------------------------- 1 | # SpringBoot2.x中的应用监控:Actuator+InFluxDB+Telegraf+Grafana 2 | 3 | > https://micrometer.io/docs/registry/influx 4 | 5 | > https://github.com/influxdata/influxdb 6 | 7 | > https://github.com/influxdata/telegraf 8 | 9 | ## 总览 10 | 11 | `Actuator` 提供端点将数据暴露出来, `Prometheus` 定时去拉取数据并保存和提供搜索和展示, `Grafana` 提供更加精美的图像化展示 12 | 13 | ## Actuator 14 | 15 | [SpringBoot2.x使用Actuator来做应用监控](https://blog.csdn.net/myherux/article/details/80670557) 16 | 17 | ## InFluxDB 18 | 19 | 20 | 21 | ## Telegraf -------------------------------------------------------------------------------- /研发相关/Spring/核心/Spring核心接口Ordered的实现及应用.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | ## 参考 5 | 6 | - [一文带你了解Spring核心接口Ordered的实现及应用](https://juejin.im/post/6844904180214136846) 7 | 8 | - [Spring核心接口之Ordered](https://segmentfault.com/a/1190000012455485) -------------------------------------------------------------------------------- /研发相关/中间件/Kafka/Kafka处理脏读和幻读.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MyHerux/code-note/854b0f86d319886564c00c21040a1745f88b10a2/研发相关/中间件/Kafka/Kafka处理脏读和幻读.md -------------------------------------------------------------------------------- /研发相关/中间件/Kafka/Kafka如何保证消息不丢失,且只被消费一次.md: -------------------------------------------------------------------------------- 1 | 2 | 1. 消息在写到消息队列的过程中丢失 3 | 2. 消息在消息队列中丢失 4 | 3. 在消费的过程中存在消息丢失的可能 5 | 6 | 7 | 在生产、消费过程中增加消息幂等性的保证 -------------------------------------------------------------------------------- /研发相关/中间件/Kafka/Kafka添加Header.md: -------------------------------------------------------------------------------- 1 | https://cwiki.apache.org/confluence/display/KAFKA/KIP-82+-+Add+Record+Headers -------------------------------------------------------------------------------- /研发相关/中间件/Kafka/Mac安装Kafka.md: -------------------------------------------------------------------------------- 1 | - brew 安装 2 | 3 | ``` 4 | brew install kafka 5 | ``` 6 | 7 | > 注意:安装会依赖 zookeeper 8 | > zookeeper -> /usr/local/Cellar/zookeeper/3.4.13 9 | > kafka -> /usr/local/Cellar/kafka/2.0.0 10 | 11 | - 启动 12 | 13 | background service: 14 | 15 | ``` 16 | brew services start zookeeper 17 | 18 | brew services start kafka 19 | ``` 20 | 21 | or 22 | 23 | ``` 24 | zkServer start 25 | 26 | zookeeper-server-start /usr/local/etc/kafka/zookeeper.properties & kafka-server-start /usr/local/etc/kafka/server.properties 27 | ``` 28 | 29 | - create topic 30 | 31 | ``` 32 | kafka-topics --create --zookeeper localhost:2181 --replication-factor 1 --partitions 1 --topic test 33 | ``` 34 | 35 | - 查看创建的topic 36 | 37 | ``` 38 | kafka-topics --list --zookeeper localhost:2181 39 | ``` 40 | 41 | - 生产与消费 42 | 43 | 打开生产者客户端: 44 | 45 | ``` 46 | cd /usr/local/Cellar/kafka/2.0.0/bin 47 | 48 | kafka-console-producer --broker-list localhost:9092 --topic test 49 | ``` 50 | 51 | 打开消费者客户端: 52 | 53 | ``` 54 | cd /usr/local/Cellar/kafka/2.0.0/bin 55 | 56 | kafka-console-consumer --bootstrap-server localhost:9092 --topic test --from-beginning 57 | ``` 58 | 59 | 在生产者客户端键入消息,即可在消费者客户端收到相应的消息。 60 | ![](https://imgconvert.csdnimg.cn/aHR0cDovL2Nkbi5oZXJveHUuY29tLzIwMTgxMTE1MTU0MjI3NDg5NTE2NTY2LnBuZw?x-oss-process=image/format,png) 61 | 62 | ## 问题 63 | 64 | - Broker may not be available. 65 | 66 | ``` 67 | $ echo dump | nc localhost 2181 68 | SessionTracker dump: 69 | Session Sets (3): 70 | 0 expire at Thu Jan 01 23:27:08 CST 1970: 71 | 0 expire at Thu Jan 01 23:27:10 CST 1970: 72 | 1 expire at Thu Jan 01 23:27:12 CST 1970: 73 | 0x10003502b360000 74 | ephemeral nodes dump: 75 | Sessions with Ephemerals (1): 76 | 0x10003502b360000: 77 | /controller 78 | /brokers/ids/0 79 | ``` 80 | 81 | ``` 82 | $ /usr/local/Cellar/kafka/2.0.0/bin/zookeeper-shell localhost:2181 <<< "get /brokers/ids/0" 83 | Connecting to localhost:2181 84 | Welcome to ZooKeeper! 85 | JLine support is disabled 86 | 87 | WATCHER:: 88 | 89 | WatchedEvent state:SyncConnected type:None path:null 90 | {"listener_security_protocol_map":{"PLAINTEXT":"PLAINTEXT"},"endpoints":["PLAINTEXT://10.29.129.13:9092"],"jmx_port":-1,"host":"10.29.129.13","timestamp":"1542613502150","port":9092,"version":4} 91 | cZxid = 0xf6 92 | ctime = Mon Nov 19 15:45:02 CST 2018 93 | mZxid = 0xf6 94 | mtime = Mon Nov 19 15:45:02 CST 2018 95 | pZxid = 0xf6 96 | cversion = 0 97 | dataVersion = 0 98 | aclVersion = 0 99 | ephemeralOwner = 0x10003502b360000 100 | dataLength = 194 101 | numChildren = 0 102 | ``` 103 | 104 | `zk host 改变,修改host文件` 105 | 106 | ``` 107 | sudo vim /etc/hosts 108 | ``` 109 | 110 | ## 其他 111 | 112 | - 查看所有 topic 113 | 114 | ``` 115 | /usr/local/Cellar/kafka/2.0.0/bin/kafka-topics --zookeeper 127.0.0.1:2181 --list 116 | ``` 117 | 118 | - 创建 topic 119 | 120 | ``` 121 | kafka-topics --create \ 122 | --zookeeper localhost:2181 \ 123 | --replication-factor 1 \ 124 | --partitions 1 \ 125 | --topic streams-plaintext-input 126 | ``` 127 | 128 | - 查看 topic 详细描述 129 | 130 | ``` 131 | kafka-topics --zookeeper localhost:2181 --describe 132 | ``` 133 | -------------------------------------------------------------------------------- /研发相关/中间件/Kafka/README.md: -------------------------------------------------------------------------------- 1 | ## Kafka 2 | 3 | - [流式Json数据生成器](./流式Json数据生成器.md) 4 | 5 | - [Kafka-Streams-Wiondowing](./Kafka-Streams-Wiondowing.md) 6 | 7 | - [Mac安装Kafka](./Mac安装Kafka.md) 8 | 9 | - [Springboot+Kafka](./Springboot+Kafka.md) -------------------------------------------------------------------------------- /研发相关/中间件/Kafka/Springboot+Kafka.md: -------------------------------------------------------------------------------- 1 | # Springboot+Kafka 2 | 3 | ## 1. Overview 4 | 5 | - [Kafka Documentation](http://kafka.apache.org/documentation/) 6 | - [Spring for Apache Kafka](https://docs.spring.io/spring-kafka/reference/html/) 7 | 8 | ## 2. Kafka 搭建 9 | 10 | - [Mac 安装 Kafka](https://blog.csdn.net/MyHerux/article/details/84108223) 11 | 12 | ## 3. 初试 13 | 14 | - 依赖 15 | 16 | ``` 17 | 18 | org.springframework.kafka 19 | spring-kafka 20 | 2.1.7.RELEASE 21 | 22 | ``` 23 | 24 | - 配置 25 | 26 | ``` 27 | spring: 28 | kafka: 29 | bootstrap-servers: localhost:9092 30 | consumer: 31 | group-id: myGroup 32 | ``` 33 | 34 | - 消费 35 | 36 | `topic` 为测试时建立的 `topic` 37 | 38 | ``` 39 | @Component 40 | public class KafkaConsumer { 41 | 42 | @KafkaListener(topics = "test") 43 | public void consume(String content){ 44 | System.out.println(content); 45 | } 46 | } 47 | ``` 48 | 49 | - 启动 50 | 51 | 启动项目,并且在前面自己安装的 `Kafka Producer` 产生消息,可以看到启动的项目里面收到消息: 52 | 53 | ![](http://cdn.heroxu.com/20181115154227642642510.png) 54 | 55 | 56 | ## 4. Kafka架构 57 | 58 | ### 4.1. 术语 59 | 60 | - Broker 61 | 62 | `Kafka` 集群包含一个或多个服务器,这种服务器被称为 `broker` 63 | 64 | - Topic 65 | 66 | 每条发布到 `Kafka` 集群的消息都有一个类别,这个类别被称为 `Topic` 。(物理上不同 `Topic` 的消息分开存储,逻辑上一个 `Topic` 的消息虽然保存于一个或多个 `broker` 上但用户只需指定消息的 `Topic` 即可生产或消费数据而不必关心数据存于何处) 67 | 68 | - Partition 69 | 70 | `Parition` 是物理上的概念,每个 `Topic` 包含一个或多个 `Partition`. 71 | 72 | - Producer 73 | 74 | 负责发布消息到 `Kafka broker` 75 | 76 | - Consumer 77 | 78 | 消息消费者,向 `Kafka broker` 读取消息的客户端。 79 | 80 | - Consumer Group 81 | 82 | 每个 `Consumer` 属于一个特定的 `Consumer Group`(可为每个 `Consumer` 指定 `groupname` ,若不指定 `group name` 则属于默认的 `group`)。 83 | 84 | 85 | ## 5. 更多用法 86 | 87 | - 多消费者组消费同一条消息 88 | 89 | 根据 `Kafka` 的设计原理可知,如果两个不同的 `consumer` 分别处于两个不同的 `consumer group` ,那么它们就可以同时消费同一条消息: 90 | 91 | ``` 92 | @KafkaListener(topics = "test",groupId = "myGroup") 93 | public void consume(String content){ 94 | System.out.println("myGroup message: "+content); 95 | } 96 | 97 | @KafkaListener(topics = "test",groupId = "myGroup2") 98 | public void consume2(String content){ 99 | System.out.println("myGroup2 message: "+content); 100 | } 101 | ``` 102 | 103 | - 批量消费消息 104 | 105 | 配置: 106 | 107 | ``` 108 | @Bean("batchContainerFactory") 109 | public KafkaListenerContainerFactory batchContainerFactory() { 110 | ConcurrentKafkaListenerContainerFactory containerFactory = new ConcurrentKafkaListenerContainerFactory<>(); 111 | containerFactory.setConsumerFactory(consumerFactory()); 112 | containerFactory.setConcurrency(4); 113 | containerFactory.setBatchListener(true); //批量消费 114 | 115 | return containerFactory; 116 | } 117 | ``` 118 | 119 | 消费: 120 | 121 | ``` 122 | @KafkaListener(topics = "test", groupId = "myGroup3", containerFactory = "batchContainerFactory") 123 | public void consume3(List content) { 124 | System.out.println("myGroup3 list->string message: " + content.stream().reduce((a, b) -> a + b).get()); 125 | } 126 | ``` 127 | -------------------------------------------------------------------------------- /研发相关/中间件/Kafka/流式Json数据生成器.md: -------------------------------------------------------------------------------- 1 | # 流式Json数据生成器 2 | 3 | ## Overview 4 | 5 | 在看法流式应用处理时,经常需要一些流式数据来测试,自己生成这些数据比较麻烦,可以使用 `json-data-generator` 来帮助生成。 6 | 7 | ## 项目地址 8 | 9 | - [Github](https://github.com/everwatchsolutions/json-data-generator) 10 | 11 | ## 配置 Kafka 数据 12 | 13 | - jackieChan.config 14 | 15 | 需要生成的 `kafka` 数据配置。 16 | 17 | ``` 18 | { 19 | "workflows": [{ 20 | "workflowName": "jackieChan", 21 | "workflowFilename": "jackieChanWorkflow.json" 22 | }], 23 | "producers": [{ 24 | "type": "kafka", 25 | "broker.server": "192.168.59.103", 26 | "broker.port": 9092, 27 | "topic": "jackieChanCommand", 28 | "flatten": false, 29 | "sync": false 30 | 31 | }] 32 | } 33 | ``` 34 | 35 | - jackieChanWorkflow.json 36 | 37 | 配置每一次生成的数据。 38 | 39 | ``` 40 | { 41 | "eventFrequency": 400, 42 | "varyEventFrequency": true, 43 | "repeatWorkflow": true, 44 | "timeBetweenRepeat": 1500, 45 | "varyRepeatFrequency": true, 46 | "steps": [{ 47 | "config": [{ 48 | "timestamp": "now()", 49 | "style": "random('KUNG_FU','WUSHU','DRUNKEN_BOXING')", 50 | "action": "random('KICK','PUNCH','BLOCK','JUMP')", 51 | "weapon": "random('BROAD_SWORD','STAFF','CHAIR','ROPE')", 52 | "target": "random('HEAD','BODY','LEGS','ARMS')", 53 | "strength": "double(1.0,10.0)" 54 | } 55 | ], 56 | "duration": 0 57 | }] 58 | } 59 | ``` 60 | 61 | ## 数据生成流程 62 | 63 | - 如果您还没有,请继续下载最新版本的 [json-data-generator](https://github.com/everwatchsolutions/json-data-generator/releases)。 64 | 65 | - 将下载的文件解压缩到目录中。 66 | 67 | - 将自定义配置复制到 `conf` 目录中 68 | 69 | - 然后像这样运行生成器: 70 | 71 | ``` 72 | java -jar json-data-generator-1.4.0.jar jackieChan.json 73 | ``` -------------------------------------------------------------------------------- /研发相关/中间件/分布式环境下保持数据一致性.md: -------------------------------------------------------------------------------- 1 | # 分布式环境下保持数据一致性 2 | 3 | ## 业务场景 4 | 5 | 订单和支付是两个独立的平台,相互之间通过消息队列(kafka)进行通信 -------------------------------------------------------------------------------- /研发相关/中间件/消息队列之事务消息.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | [消息队列之事务消息,RocketMQ 和 Kafka 是如何做的](https://juejin.im/post/6867040340797292558) 7 | 8 | [kafka系列九、kafka事务原理、事务API和使用场景](https://www.cnblogs.com/wangzhuxing/p/10125437.html) 9 | 10 | [Kafka设计-恰好一次和事务消息](https://cloud.tencent.com/developer/article/1600510) 11 | 12 | [还不知道事务消息吗?这篇文章带你全面扫盲!](https://juejin.im/post/6844904106532962311) -------------------------------------------------------------------------------- /研发相关/分布式系统/README.md: -------------------------------------------------------------------------------- 1 | ## 分布式 2 | 3 | - [分布式环境下保持数据一致性](./分布式环境下保持数据一致性.md) 4 | 5 | - [CAP原则](./CAP原则.md) -------------------------------------------------------------------------------- /研发相关/分布式系统/分布式事务/BASE理论.md: -------------------------------------------------------------------------------- 1 | # BASE理论 2 | 3 | `BASE` 理论指的是基本可用 `Basically Available` ,软状态 `Soft State` ,最终一致性 `Eventual Consistency` ,核心思想是即便无法做到强一致性,但应该采用适合的方式保证最终一致性。 4 | 5 | `BASE` 理论本质上是对 `CAP` 理论的延伸,是对 `CAP` 中 `AP` 方案的一个补充。 6 | 7 | ## Basically Available 基本可用 8 | 9 | 分布式系统在出现故障的时候,允许损失部分可用性,即保证核心可用。 10 | 11 | - 响应时间上的损失 12 | 13 | 正常情况下,一个在线搜索引擎需要0.5秒内返回给用户相应的查询结果,但由于出现异常(比如系统部分机房发生断电或断网故障),查询结果的响应时间增加到了1~2秒。 14 | 15 | - 功能上的损失 16 | 17 | 正常情况下,在一个电子商务网站上进行购物,消费者几乎能够顺利地完成每一笔订单,但是在一些节日大促购物高峰的时候,由于消费者的购物行为激增,为了保护购物系统的稳定性,部分消费者可能会被引导到一个降级页面。 18 | 19 | ## Soft State 软状态 20 | 21 | 允许系统存在中间状态,而该中间状态不会影响系统整体可用性。 22 | 23 | ## Eventual Consistency 最终一致性 24 | 25 | 系统中的所有数据副本经过一定时间后,最终能够达到一致的状态。 26 | 27 | 在实际工程实践中,最终一致性存在以下五类主要变种。 28 | 29 | - 因果一致性 30 | 31 | 如果进程A在更新完某个数据项后通知了进程B,那么进程B之后对该数据项的访问都应该能够获取到进程A更新后的最新值,并且如果进程B要对该数据项进行更新操作的话,务必基于进程A更新后的最新值,即不能发生丢失更新情况。与此同时,与进程A无因果关系的进程C的数据访问则没有这样的限制。 32 | 33 | - 读己之所写 34 | 35 | 进程A更新一个数据项之后,它自己总是能够访问到更新过的最新值,而不会看到旧值。也就是说,对于单个数据获取者而言,其读取到的数据一定不会比自己上次写入的值旧。因此,读己之所写也可以看作是一种特殊的因果一致性。 36 | 37 | - 会话一致性 38 | 39 | 将对系统数据的访问过程框定在一个会话当中:系统能保证在同一个有效的会话中实现“读己之所写”的一致性,也就是说,执行更新操作之后,客户端能够在同一个会话中始终读取到该数据项的最新值。 40 | 41 | - 单调读一致性 42 | 43 | 如果一个进程从系统中读取出一个数据项的某个值后,那么系统对于该进程后续的任何数据访问都不应该返回更旧的值。 44 | 45 | - 单调写一致性 46 | 47 | 一个系统需要能够保证来自同一个进程的写操作被顺序地执行。 48 | 49 | ## 参考 50 | 51 | - [分布式系统的BASE理论](https://www.hollischuang.com/archives/672) 52 | 53 | - [CAP原则(CAP定理)、BASE理论](https://www.cnblogs.com/duanxz/p/5229352.html) 54 | 55 | - [分布式理论(二) - BASE理论](https://juejin.cn/post/6844903621495095304) 56 | 57 | -------------------------------------------------------------------------------- /研发相关/分布式系统/分布式事务/CAP原则.md: -------------------------------------------------------------------------------- 1 | # CAP原则 2 | 3 | ## Overview 4 | 5 | `CAP` 原则又称 `CAP` 定理,指的是在一个分布式系统中, `Consistency`(一致性)、`Availability`(可用性)、`Partition tolerance`(分区容错性),三者不可得兼,最多只能同时满足其中的 `2` 个。 6 | 7 | ![20191212103254](http://cdn.heroxu.com/20191212103254.png) 8 | 9 | - 一致性(`Consistency`) 10 | 11 | 在分布式系统中的所有数据备份,在同一时刻是否同样的值。(严格的一致性,所有节点访问同一份最新的数据副本) 12 | 13 | - 可用性(`Availability`) 14 | 15 | 在集群中一部分节点故障后,集群整体是否还能响应客户端的读写请求。(对数据更新具备高可用性,不保证获取的数据为最新数据,但是保证最终一致性) 16 | 17 | - 分区容错性(`Partition tolerance`) 18 | 19 | 分布式系统在遇到任何网络分区故障的时候,仍然能够对外提供满足一致性和可用性的服务,除非整个网络环境都发生了故障。系统如果不能在时限内达成数据一致性,就意味着发生了分区的情况,必须就当前操作在 `C` 和 `A` 之间做出选择。 20 | 21 | ## CAP原则论证 22 | 23 | #### 基本场景 24 | 25 | 在一个CAP的基本场景中,网络中有两个节点N1和N2,可以简单的理解N1和N2分别是两台计算机,他们之间网络可以连通,N1中有一个应用程序A,和一个数据库V,N2也有一个应用程序B2和一个数据库V。现在,A和B是分布式系统的两个部分,V是分布式系统的数据存储的两个子数据库。 26 | 27 | ![20191212110038](http://cdn.heroxu.com/20191212110038.png) 28 | 29 | - 在满足一致性的时候,N1和N2中的数据是一样的,V0=V0。 30 | - 在满足可用性的时候,用户不管是请求N1或者N2,都会得到立即响应。 31 | - 在满足分区容错性的情况下,N1和N2有任何一方宕机,或者网络不通的时候,都不会影响N1和N2彼此之间的正常运作。 32 | 33 | #### 网络正常运行,实际上同时满足CAP 34 | 35 | ![20191212111643](http://cdn.heroxu.com/20191212111643.png) 36 | 37 | 用户向N1机器请求数据更新,程序A更新数据库V0为V1。分布式系统将数据进行同步操作M,将V1同步的N2中V0,使得N2中的数据V0也更新为V1,N2中的数据再响应N2的请求。 38 | 39 | - 一致性:N1和N2的数据库V之间的数据是否完全一样。 40 | - 可用性:N1和N2的对外部的请求能否做出正常的响应。 41 | - 分区容错性:N1和N2之间的网络是否互通。 42 | 43 | #### 网络异常,CAP只能同时满足其中2个 44 | 45 | ![20191212111844](http://cdn.heroxu.com/20191212111844.png) 46 | 47 | 假设在N1和N2之间网络断开的时候,有用户向N1发送数据更新请求,那N1中的数据V0将被更新为V1。由于网络是断开的,所以分布式系统同步操作M,所以N2中的数据依旧是V0。这个时候,有用户向N2发送数据读取请求,由于数据还没有进行同步,应用程序没办法立即给用户返回最新的数据V1,怎么办呢? 48 | 这里有两种选择: 49 | 50 | - 第一:牺牲数据一致性,保证可用性。响应旧的数据V0给用户。 51 | - 第二:牺牲可用性,保证数据一致性。阻塞等待,直到网络连接恢复,数据更新操作M完成之后,再给用户响应最新的数据V1。 52 | 53 | #### 总结 54 | 55 | 实际上,对于分布式系统来说,并不是 `CAP` 只能同时满足其中的 `2` 个,而是当网络出现问题时:因为 `P` 必须满足,所以只能再 `A` 和 `C` 中二选一。 56 | 57 | ## 取舍策略 58 | 59 | 因为 `CAP` 最多只能同时满足其中的 `2` 个,所以不得不做个取舍。 60 | 61 | - CA without P 62 | 63 | 如果不要求P(不允许分区),则C(强一致性)和A(可用性)是可以保证的。但放弃P的同时也就意味着放弃了系统的扩展性,也就是分布式节点受限,没办法部署子节点,这是违背分布式系统设计的初衷的。传统的关系型数据库RDBMS:Oracle、MySQL就是CA。 64 | 65 | - CP without A 66 | 67 | 如果不要求A(可用),相当于每个请求都需要在服务器之间保持强一致,而P(分区)会导致同步时间无限延长(也就是等待数据同步完才能正常访问服务),一旦发生网络故障或者消息丢失等情况,就要牺牲用户的体验,等待所有数据全部一致了之后再让用户访问系统。设计成CP的系统其实不少,最典型的就是分布式数据库,如Redis、HBase等。对于这些分布式数据库来说,数据的一致性是最基本的要求,因为如果连这个标准都达不到,那么直接采用关系型数据库就好,没必要再浪费资源来部署分布式数据库。 68 | 69 | - AP wihtout C 70 | 71 | 要高可用并允许分区,则需放弃一致性。一旦分区发生,节点之间可能会失去联系,为了高可用,每个节点只能用本地数据提供服务,而这样会导致全局数据的不一致性。典型的应用就如某米的抢购手机场景,可能前几秒你浏览商品的时候页面提示是有库存的,当你选择完商品准备下单的时候,系统提示你下单失败,商品已售完。这其实就是先在 A(可用性)方面保证系统可以正常的服务,然后在数据的一致性方面做了些牺牲,虽然多少会影响一些用户体验,但也不至于造成用户购物流程的严重阻塞。 72 | 73 | ## 主流分布式系统是如何选择的 74 | 75 | | | Eureka | Consul | Zookeeper | Nacos | Etcd | 76 | | ---------- | ------ | ------ | -------------------- | ----- | ---- | 77 | | CAP | AP | CP | CP | AP/CP | CP | 78 | | 一致性算法 | 无 | Raft | ZAB(类 PAXOS 协议) | Raft | Raft | 79 | 80 | 对于`AP` 来说实际上,不用关心一致性算法,所以 `Eureka` 中没有使用任何的数据强一致性算法保证不同集群间的 `Server` 的数据一致,仅通过数据拷贝的方式争取注册中心数据的最终一致性。 81 | 82 | 而像 `Zookeeper` 这种分布式协调组件,数据的一致性是他们最最基本的要求。所以在极端环境下, `ZooKeeper` 可能会丢弃一些请求,消费者程序需要重新请求才能获得结果,也要保证数据一致性。 83 | 84 | 对于 `Nacos` 来说,实现 `AP` 的同时,也使用了一致性算法 `Raft` ,[Nacos 是如何同时实现AP与CP的](https://www.liaochuntao.cn/2019/06/01/java-web-41/)。 85 | 86 | ## 延伸阅读 87 | 88 | 对于一个分布式系统来说。 `P` 是一个基本要求, `CAP` 三者中,只能在 `CA` 两者之间做权衡,并且要想尽办法提升 `P` 。 89 | 某些情况下,`AP` 与 `CP` 的选择,可以看下这些公司是如何选择的: 90 | 91 | - [Eureka! Why You Shouldn’t Use ZooKeeper for Service Discovery](https://medium.com/knerd/eureka-why-you-shouldnt-use-zookeeper-for-service-discovery-4932c5c7e764) 92 | 93 | - [阿里巴巴为什么不用 ZooKeeper 做服务发现?](http://jm.taobao.org/2018/06/13/%E5%81%9A%E6%9C%8D%E5%8A%A1%E5%8F%91%E7%8E%B0%EF%BC%9F/) 94 | 95 | 96 | ## 参考 97 | 98 | - [CAP原则(CAP定理)、BASE理论](https://www.cnblogs.com/duanxz/p/5229352.html) 99 | 100 | - [CAP Theorem: Revisited](https://robertgreiner.com/cap-theorem-revisited/) 101 | 102 | - [An Illustrated Proof of the CAP Theorem](https://mwhittaker.github.io/blog/an_illustrated_proof_of_the_cap_theorem/) 103 | 104 | - [CAP 定理的含义](https://www.ruanyifeng.com/blog/2018/07/cap.html) 105 | 106 | - [分布式系统的CAP理论](https://www.hollischuang.com/archives/666) -------------------------------------------------------------------------------- /研发相关/分布式系统/分布式事务/SAGA事务模型.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MyHerux/code-note/854b0f86d319886564c00c21040a1745f88b10a2/研发相关/分布式系统/分布式事务/SAGA事务模型.md -------------------------------------------------------------------------------- /研发相关/分布式系统/分布式事务/XA协议与二阶段提交.md: -------------------------------------------------------------------------------- 1 | # XA协议与二阶段提交 2 | 3 | ## XA协议 4 | 5 | `XA` 的全称是 `eXtended Architecture`,它是一个分布式事务协议,通过二阶段提交协议保证强一致性。 `XA` 协议定义了交易中间件与数据库之间的接口规范(即接口函数),交易中间件用它来通知数据库事务的开始、结束以及提交、回滚等。而 `XA` 接口函数由数据库厂商提供。 6 | 7 | ### 核心模型 8 | 9 | `XA` 规范中定义了分布式事务处理模型,这个模型中包含四个核心角色: 10 | 11 | - RM (Resource Managers) 12 | 13 | 资源管理器,提供数据资源的操作、管理接口,保证数据的一致性和完整性。最有代表性的就是数据库管理系统,当然有的文件系统、`MQ` 系统也可以看作 `RM` 。 14 | 15 | - TM (Transaction Managers) 16 | 17 | 事务管理器,是一个协调者的角色,协调跨库事务关联的所有 `RM` 的行为。一般是交易中间件。 18 | 19 | - AP (Application Program) 20 | 21 | 应用程序,按照业务规则调用 `RM` 接口来完成对业务模型数据的变更,当数据的变更涉及多个 `RM` 且要保证事务时, `AP` 就会通过 `TM` 来定义事务的边界, `TM` 负责协调参与事务的各个 `RM` 一同完成一个全局事务。 22 | 23 | - CRMs (Communication Resource Managers) 24 | 25 | 主要用来进行跨服务的事务的传播。一般是消息中间件。 26 | 27 | 28 | ## 二阶段提交(2PC) 29 | 30 | `2PC` 全称 `Two-phaseCommit` ,中文名是二阶段提交,是 `XA` 规范的实现思路。二阶段提交,顾名思义就是要分两步提交。存在一个负责协调各个本地资源管理器的事务管理器,本地资源管理器一般是由数据库实现,事务管理器在第一阶段的时候询问各个资源管理器是否都就绪?如果收到每个资源的回复都是 `yes` ,则在第二阶段提交事务,如果其中任意一个资源的回复是 `no` ,则回滚事务。 31 | 32 | ![20201221155429](http://cdn.heroxu.com/20201221155429.png) 33 | 34 | ### 事务流程 35 | 36 | - 第一阶段(prepare) 37 | 38 | 事务管理器向所有本地资源管理器发起请求,询问是否是 `ready` 状态,所有参与者都将本事务能否成功的信息反馈发给协调者; 39 | 40 | - 第二阶段 (commit/rollback) 41 | 42 | 事务管理器根据所有本地资源管理器的反馈,通知所有本地资源管理器,步调一致地在所有分支上提交或者回滚。 43 | 44 | ## XA实现如何保证ACID 45 | 46 | ### 原子性(Atomicity) 47 | 48 | `2PC` 可以保证每次事务的原子性。 49 | 50 | ### 隔离性(Isolation) 51 | 52 | `XA` 协议中没有描述如何实现分布式事务的隔离性,但是 `XA` 协议要求每个资源管理器都要实现本地事务,也就是说基于 `XA` 协议实现的分布式事务的隔离性是由每个资源管理器本地事务的隔离性来保证的,当一个分布式事务的所有子事务都是隔离的,那么这个分布式事务天然的就实现了隔离性。 53 | 54 | 以 `MySQL` 来举例, `MySQL` 使用 `2PL`( `Two-PhaseLocking` ,两阶段锁)机制来控制本地事务的并发,保证隔离性。 `2PL` 与 `2PC` 类似,也是将锁操作分为加锁和解锁两个阶段,并且保证两个阶段完全不相交。加锁阶段,只加锁,不放锁。解锁阶段,只放锁,不加锁。 55 | 56 | ![20201221170713](http://cdn.heroxu.com/20201221170713.png) 57 | 58 | 如上图所示,在一个本地事务中,每执行一条更新操作之前,都会先获取对应的锁资源,只有获取锁资源成功才会执行该操作,并且一旦获取了锁资源就会持有该锁资源直到本事务执行结束。 `MySQL` 通过这种 `2PL` 机制,可以保证在本地事务执行过程中,其他并发事务不能操作相同资源,从而实现了事务隔离。 59 | 60 | 61 | ### 一致性(Consistency) 62 | 63 | 一致性有两层语义,一层是确保事务执行结束后,数据库从一个一致状态转变为另一个一致状态,数据最终一致。另一层语义是事务执行过程中的中间状态不能被别的事务观察到。 64 | 65 | 前一层语义的实现很简单,通过原子性、隔离性以及 `RM` 自身一致性的实现就可以保证。至于后一层语义,我们先来看看单个 `RM` 上的本地事务是怎么实现的。还是以 `MySQL` 举例, `MySQL` 通过 `MVCC`( `Multi Version Concurrency Control` ,多版本并发控制)机制,为每个一致性状态生成快照(Snapshot),每个事务看到的都是各 `Snapshot` 对应的一致性状态,从而也就保证了本地事务的中间状态不会被观察到。 66 | 67 | 虽然单个 `RM` 上实现了 `Snapshot` ,但是在分布式应用架构下,会遇到什么问题呢? 68 | 69 | ![20201221180544](http://cdn.heroxu.com/20201221180544.png) 70 | 71 | 如上图所示,在 `RM1` 的本地子事务提交完毕到 `RM2` 的本地子事务提交完毕之间,只能读到 `RM1` 上子事务执行的内容,读不到 `RM2` 上的子事务。也就是说,虽然在单个 `RM` 上的本地事务是一致的,但是从全局来看,一个全局事务执行过程的中间状态被观察到了,全局一致性就被破坏了。 72 | 73 | `XA` 协议并没有定义怎么实现全局的 `Snapshot` ,像 `MySQL` 官方文档里就建议使用串行化的隔离级别来保证分布式事务一致性: `“As with nondistributed transactions, SERIALIZABLE may be preferred if your applications are sensitive to read phenomena. REPEATABLE READ may not be sufficient for distributed transactions.”`(对于分布式事务来说,可重复读隔离级别不足以保证事务一致性,如果你的程序有全局一致性读要求,可以考虑串行化隔离级别.) 74 | 75 | 当然,由于串行化隔离级别的性能较差,所以很多分布式数据库都自己实现了分布式 `MVCC` 机制来提供全局的一致性读。一个基本思路是用一个集中式或者逻辑上单调递增的东西来控制生成全局 `Snapshot` ,每个事务或者每条 `SQL` 执行时都去获取一次,从而实现不同隔离级别下的一致性。比如 `Google` 的 `Spanner` 就是用 `TrueTime` 来控制访问全局 `Snapshot` 。 76 | 77 | ## XA实现存在的问题 78 | 79 | - 同步阻塞:当参与事务者存在占用公共资源的情况,其中一个占用了资源,其他事务参与者就只能阻塞等待资源释放,处于阻塞状态。 80 | 81 | - 单点故障:一旦事务管理器出现故障,整个系统不可用。 82 | 83 | - 数据不一致:在阶段二,如果事务管理器只发送了部分 `commit` 消息,此时网络发生异常,那么只有部分参与者接收到 `commit` 消息,也就是说只有部分参与者提交了事务,使得系统数据不一致。 84 | 85 | - 不确定性:当事务管理器发送 `commit` 之后,并且此时只有一个参与者收到了 `commit` ,那么当该参与者与事务管理器同时宕机之后,重新选举的事务管理器无法确定该条消息是否提交成功。 86 | 87 | 88 | ## 参考 89 | 90 | - [分布式理论(三) - 2PC协议](https://juejin.cn/post/6844903621495095309#heading-7) 91 | 92 | - [浅谈事务和一致性:刚性or柔性?](https://juejin.cn/post/6844903575756210184) 93 | 94 | - [趁热打铁-再谈分布式事务](https://www.cnblogs.com/rickiyang/p/13704868.html) 95 | 96 | - [一篇文章带你学习分布式事务](https://www.infoq.cn/article/g1avp9fua6cdoyralv4r) 97 | 98 | - [分布式事务之解决方案(XA和2PC)](https://cloud.tencent.com/developer/article/1547146) -------------------------------------------------------------------------------- /研发相关/分布式系统/分布式事务/事务消息.md: -------------------------------------------------------------------------------- 1 | # 事务消息 2 | 3 | 本地消息表的模式无法支持本地事务执行和消息发送一致性的问题,如果能在本地事务执行和发消息这两个操作上加上事务,那岂不是完美。 4 | 5 | 基于这个思路, 在 `MQ` 中存储消息的状态才是真理,消息生产者先把消息发送给 `MQ` ,此时消息状态为`“待确认”`,接着生产者去执行本地事务,如果执行成功就给 `MQ` 发送消息让他更改消息状态为 `“待发送”` 并发送消息,如果执行失败则删除消息。 6 | 7 | 这样就保证了本地事务和消息发送一致性问题。 8 | 9 | ## 事务消息模型 10 | 11 | ![20201222160136](http://cdn.heroxu.com/20201222160136.png) 12 | 13 | - 首先事务发起方先往 `MQ` 发送一条预读消息,这条消息与普通消息的区别在于他只对 `MQ` 可见不会向下传播。 14 | - `MQ` 接受到消息后,先进行持久化,则存储中会新增一条状态为待发送的消息,接着给事务发起方返回处理完成的 `ACK`;事务发起方收到处理完成的 `ACK` 之后开始执行本地事务。 15 | - 发起方会根据本地事务的执行状态来决定这个预读消息是应该继续往前还是回滚。另外 `MQ` 也应该支持自己反查来解决异常情况,如果发起方本地事务已经执行完毕发送消息到MQ,但是消息因为网络原因丢失,那么怎么解决。所以这个反查机制很重要。 16 | - 本地事务执行成功以后, `MQ` 也接收到成功通知,接着将消息状态更新为可发送,然后将消息推送给下游的消费者,这个时候消费者就可以去处理自己的本地事务 。 17 | 18 | 19 | ## 事务消息使用注意点 20 | 21 | - 消息回查 22 | 23 | 事务执行之后的消息可能会有投递失败的情况,需要允许 `MQ` 反查这个事务是否成功,没成功就返回未知。因为有可能事务还在执行,会进行多次查询。 24 | 25 | - 消费幂等 26 | 27 | 由于 `MQ` 通常都会保证消息能够投递成功,因此,如果业务没有及时返回 `ACK` 结果,那么就有可能造成 `MQ` 的重复消息投递问题。因此,对于消息最终一致性的方案,消息的消费者必须要对消息的消费支持幂等,不能造成同一条消息的重复消费的情况。 28 | 29 | 30 | ## 参考 31 | 32 | - [Kafka 事务特性分析](https://zhuanlan.zhihu.com/p/42046847) 33 | 34 | - [大写的服,看完这篇你还不懂RocketMQ算我输](https://jishuin.proginn.com/p/763bfbd2f1c5) 35 | 36 | - [还不知道事务消息吗?这篇文章带你全面扫盲!](https://juejin.cn/post/6844904106532962311) 37 | 38 | - [消息队列之事务消息,RocketMQ 和 Kafka 是如何做的](https://juejin.im/post/6867040340797292558) 39 | 40 | - [kafka系列九、kafka事务原理、事务API和使用场景](https://www.cnblogs.com/wangzhuxing/p/10125437.html) 41 | 42 | - [Kafka设计-恰好一次和事务消息](https://cloud.tencent.com/developer/article/1600510) 43 | -------------------------------------------------------------------------------- /研发相关/分布式系统/分布式事务/分布式事务总览.md: -------------------------------------------------------------------------------- 1 | # 分布式事务 2 | 3 | ## OverView 4 | 5 | ### 事务 6 | 7 | 事务是应用程序中一系列严密的操作,所有操作必须成功完成,否则在每个操作中所作的所有更改都会被撤消。也就是事务具有原子性,一个事务中的一系列的操作要么全部成功,要么一个都不做。事务应该具有 4 个属性:原子性、一致性、隔离性、持久性。这四个属性通常称为 `ACID` 特性。 8 | 9 | ### 分布式事务 10 | 11 | 分布式事务是指事务的参与者、支持事务的服务器、资源服务器以及事务管理器分别位于不同的分布式系统的不同节点之上。例如在大型电商系统中,下单接口通常会扣减库存、减去优惠、生成订单 `id` ,而订单服务与库存、优惠、订单 `id` 都是不同的服务,下单接口的成功与否,不仅取决于本地的 `db` 操作,而且依赖第三方系统的结果,这时候分布式事务就保证这些操作要么全部成功,要么全部失败。本质上来说,分布式事务就是为了保证不同数据库的数据一致性。 12 | 13 | ### 强一致性、弱一致性、最终一致性 14 | 15 | **强一致性** 16 | 17 | 任何一次读都能读到某个数据的最近一次写的数据。系统中的所有进程,看到的操作顺序,都和全局时钟下的顺序一致。简言之,在任意时刻,所有节点中的数据是一样的。 18 | 19 | **弱一致性** 20 | 21 | 数据更新后,如果能容忍后续的访问只能访问到部分或者全部访问不到,则是弱一致性。 22 | 23 | **最终一致性** 24 | 25 | 不保证在任意时刻任意节点上的同一份数据都是相同的,但是随着时间的迁移,不同节点上的同一份数据总是在向趋同的方向变化。简单说,就是在一段时间后,节点间的数据会最终达到一致状态。 26 | 27 | ### CAP 原则 28 | 29 | `CAP` 原则又称 `CAP` 定理,指的是在一个分布式系统中, `Consistency`(一致性)、`Availability`(可用性)、`Partition tolerance`(分区容错性),三者不可得兼,最多只能同时满足其中的 `2` 个。 30 | 31 | ![20191212103254](http://cdn.heroxu.com/20191212103254.png) 32 | 33 | - 一致性(`Consistency`) 34 | 35 | 在分布式系统中的所有数据备份,在同一时刻是否同样的值。(严格的一致性,所有节点访问同一份最新的数据副本) 36 | 37 | - 可用性(`Availability`) 38 | 39 | 在集群中一部分节点故障后,集群整体是否还能响应客户端的读写请求。(对数据更新具备高可用性,不保证获取的数据为最新数据,但是保证最终一致性) 40 | 41 | - 分区容错性(`Partition tolerance`) 42 | 43 | 分布式系统在遇到任何网络分区故障的时候,仍然能够对外提供满足一致性和可用性的服务,除非整个网络环境都发生了故障。系统如果不能在时限内达成数据一致性,就意味着发生了分区的情况,必须就当前操作在 `C` 和 `A` 之间做出选择。 44 | 45 | ### BASE理论 46 | 47 | `BASE` 理论指的是基本可用 `Basically Available` ,软状态 `Soft State` ,最终一致性 `Eventual Consistency` ,核心思想是即便无法做到强一致性,但应该采用适合的方式保证最终一致性。 48 | 49 | `BASE` 理论本质上是对 `CAP` 理论的延伸,是对 `CAP` 中 `AP` 方案的一个补充。 50 | 51 | - Basically Available 基本可用 52 | 53 | 分布式系统在出现故障的时候,允许损失部分可用性,即保证核心可用。 54 | 55 | - Soft State 软状态 56 | 57 | 允许系统存在中间状态,而该中间状态不会影响系统整体可用性。 58 | 59 | - Eventual Consistency 最终一致性 60 | 61 | 系统中的所有数据副本经过一定时间后,最终能够达到一致的状态。 62 | 63 | ### 刚性事务 64 | 65 | 刚性事务(如单数据库)完全遵循 `ACID` 规范: 66 | 67 | - 原子性(Atomicity) 68 | - 一致性(Consistency) 69 | - 隔离性(Isolation) 70 | - 持久性(Durability) 71 | 72 | ### 柔性事务 73 | 74 | 柔性事务(如分布式事务)为了满足可用性、性能与降级服务的需要,降低一致性(Consistency)与隔离性(Isolation)的要求。柔性事务遵循 `BASE` 理论。 75 | 76 | ### 幂等操作 77 | 78 | 在编程中一个幂等操作的特点是其任意多次执行所产生的影响均与一次执行的影响相同。幂等函数,或幂等方法,是指可以使用相同参数重复执行,并能获得相同结果的函数。这些函数不会影响系统状态,也不用担心重复执行会对系统造成改变。例如,支付流程中第三方支付系统告知系统中某个订单支付成功,接收该支付回调接口在网络正常的情况下无论操作多少次都应该返回成功。 79 | 80 | ## 分布式事务使用场景 81 | 82 | ### 转账 83 | 84 | 转账是最经典那的分布式事务场景,假设用户 `A` 使用银行 `app` 发起一笔跨行转账给用户 `B` ,银行系统首先扣掉用户 `A` 的钱,然后增加用户 `B` 账户中的余额。此时就会出现 `2` 种异常情况: 85 | 1. 用户 `A` 的账户扣款成功,用户 `B` 账户余额增加失败 86 | 2. 用户 `A` 账户扣款失败,用户 `B` 账户余额增加成功 87 | 88 | 对于银行系统来说,以上 `2` 种情况都是不允许发生,此时就需要分布式事务来保证转账操作的成功。 89 | 90 | ### 下单扣库存 91 | 92 | 在电商系统中,下单是用户最常见操作。在下单接口中必定会涉及生成订单 `id` ,扣减库存等操作,对于微服务架构系统,订单 `id` 与库存服务一般都是独立的服务,此时就需要分布式事务来保证整个下单接口的成功。 93 | 94 | ### 分库分表场景 95 | 96 | 当我们的数据量大了之后,我们可能会部署很多独立的数据库,但是你的一个逻辑可能会同时操作很多个数据库的表,这时候该如何保证所有的操作要么成功,要么失败。 97 | 98 | ### 同步超时 99 | 100 | 继续以电商系统为例,在微服务体系架构下,我们的支付与订单都是作为单独的系统存在。订单的支付状态依赖支付系统的通知,假设一个场景:我们的支付系统收到来自第三方支付的通知,告知某个订单支付成功,接收通知接口需要同步调用订单服务变更订单状态接口,更新订单状态为成功。流程图如下,从图中可以看出有两次调用,第三方支付调用支付服务,以及支付服务调用订单服务,这两步调用都可能出现调用超时的情况,此处如果没有分布式事务的保证,就会出现用户订单实际支付情况与最终用户看到的订单支付情况不一致的情况。 101 | 102 | ![20201221101838](http://cdn.heroxu.com/20201221101838.png) 103 | 104 | ## 分布式事务的解决方案 105 | 106 | ### 二阶段提交/XA 107 | 108 | `2PC` 全称 `Two-phaseCommit` ,中文名是二阶段提交,是 `XA` 规范的实现思路。二阶段提交,顾名思义就是要分两步提交。存在一个负责协调各个本地资源管理器的事务管理器,本地资源管理器一般是由数据库实现,事务管理器在第一阶段的时候询问各个资源管理器是否都就绪?如果收到每个资源的回复都是 `yes` ,则在第二阶段提交事务,如果其中任意一个资源的回复是 `no` ,则回滚事务。 109 | 110 | ![20201221155429](http://cdn.heroxu.com/20201221155429.png) 111 | 112 | > 二阶段提交是刚性事务的解决方案 113 | 114 | ### TCC 115 | 116 | `TCC` 是一种补偿型事务,该模型要求应用的每个服务提供 `try、confirm、cancel` 三个接口,它的核心思想是通过对资源的预留(提供中间态,如账户状态、冻结金额等),尽早释放对资源的加锁,如果事务可以提交,则完成对预留资源的确认,如果事务要回滚,则释放预留的资源。 117 | 118 | - Try 119 | 120 | 尝试执行业务,完成所有业务检查(一致性),预留必要的业务资源(准隔离性)。 121 | 122 | - Confirm 123 | 124 | 确认执行业务,不再做业务检查。只使用 `Try` 阶段预留的业务资源, `Confirm` 操作满足幂等性。 125 | 126 | - Cancel 127 | 128 | 取消执行业务释放 `Try` 阶段预留业务资源。 129 | 130 | ![20201222113037](http://cdn.heroxu.com/20201222113037.png) 131 | 132 | > TCC是柔性事务的解决方案 133 | 134 | ### 本地消息表 135 | 136 | ### 事务消息 137 | 138 | ### SAGA 事务模型 139 | 140 | ## 参考 141 | 142 | - [分布式事务,这一篇就够了](https://xiaomi-info.github.io/2020/01/02/distributed-transaction/) 143 | 144 | - [分布式事务解决方案与适用场景分析](https://juejin.cn/post/6844903569670275085) 145 | 146 | - [趁热打铁-再谈分布式事务](https://www.cnblogs.com/rickiyang/p/13704868.html#1496722177) -------------------------------------------------------------------------------- /研发相关/分布式系统/分布式事务/分布式环境下保持数据一致性.md: -------------------------------------------------------------------------------- 1 | 2 | # 分布式环境下保持数据一致性 3 | 4 | 5 | 6 | 7 | https://blog.csdn.net/sundacheng1989/article/details/93613270 8 | 9 | https://blog.csdn.net/floor2011/article/details/100907102 10 | 11 | -------------------------------------------------------------------------------- /研发相关/分布式系统/分布式事务/刚性事务与柔性事务.md: -------------------------------------------------------------------------------- 1 | # 刚性事务与柔性事务 2 | 3 | ## 刚性事务的定义 4 | 5 | 刚性事务(如单数据库)完全遵循 `ACID` 规范,即数据库事务正确执行的四个基本要素: 6 | 7 | - 原子性(Atomicity) 8 | - 一致性(Consistency) 9 | - 隔离性(Isolation) 10 | - 持久性(Durability) 11 | 12 | ## 柔性事务的定义 13 | 14 | 柔性事务(如分布式事务)为了满足可用性、性能与降级服务的需要,降低一致性(Consistency)与隔离性(Isolation)的要求,遵循 `BASE` 理论: 15 | 16 | - 基本业务可用性(Basic Availability) 17 | - 柔性状态(Soft state) 18 | - 最终一致性(Eventual consistency) 19 | 20 | 同样的,柔性事务也部分遵循 `ACID` 规范: 21 | 22 | - 原子性:严格遵循 23 | - 一致性:事务完成后的一致性严格遵循;事务中的一致性可适当放宽 24 | - 隔离性:并行事务间不可影响;事务中间结果可见性允许安全放宽 25 | - 持久性:严格遵循 26 | 27 | ## 刚性事务与柔性事务的实现方案 28 | 29 | ### 刚性事务 30 | 31 | - 两阶段提交 32 | 33 | `XA` 协议( `2PC、JTA、JTS` )。二阶段提交的算法思路可以概括为:每个参与者将操作成败通知协调者,再由协调者根据所有参与者的反馈情报,决定各参与者是否要提交操作还是中止操作。 34 | 35 | - 3PC 36 | 37 | 三阶段提交协议( `Three-phase commit protocol` ),是二阶段提交( `2PC` )的改进版本。三个阶段分别是:询问,然后再锁资源,最后真正提交。 38 | 39 | > 刚性事务推荐 `XA` 实现。 40 | 41 | ### 柔性事务 42 | 43 | - 补偿型(TCC/FMT) 44 | 45 | `TCC` 型事务( `Try-Confirm-Cancel` )可以归为补偿型。在 `Try` 成功的情况下,如果事务要回滚,`Cancel` 将作为一个补偿机制,回滚 `Try` 操作;`TCC` 各操作事务本地化,且尽早提交(没有两阶段约束);当全局事务要求回滚时,通过另一个本地事务实现 `“补偿”` 行为。 46 | 47 | `TCC` 是将资源层的二阶段提交协议转换到业务层,成为业务模型中的一部分。 48 | 49 | - 本地消息表 50 | 51 | 本地消息表最初是由 `eBay` 架构师 `DanPritchett` 在一篇解释 `BASE` 原理的论文[《Base:An Acid Alternative》](https://queue.acm.org/detail.cfm?id=1394128)中提及的,业界目前使用这种方案是比较多的,其核心思想是将分布式事务拆分成本地事务进行处理。 52 | 53 | 方案通过在事务主动发起方额外新建事务消息表,事务发起方处理业务和记录事务消息在本地事务中完成,轮询事务消息表的数据发送事务消息,事务被动方基于消息中间件消费事务消息表中的事务。 54 | 55 | - 异步确保型(事务消息) 56 | 57 | 将一些有同步冲突的事务操作变为异步操作,避免对数据库事务的争用,如事务消息机制。 58 | 59 | - SAGA 事务模型(状态机模式、Aop 模式) 60 | 61 | `Saga` 模型是把一个分布式事务拆分为多个本地事务,每个本地事务都有相应的执行模块和补偿模块(对应 `TCC` 中的 `Confirm` 和 `Cancel` ),当 `Saga` 事务中任意一个本地事务出错时,可以通过调用相关的补偿方法恢复之前的事务,达到事务最终一致性。 62 | 63 | - 最大努力通知型 64 | 65 | 通过通知服务器(消息通知)进行,允许失败,有补充机制。 66 | 67 | > 柔性事务通常推荐同步 `Saga`、异步事务消息 68 | 69 | ## 分布式事务对比 70 | 71 | | | 本地事务 | 刚性事务 | 柔性事务 | 72 | | -------- | ---------------- | --------------- | --------------- | 73 | | 业务改造 | 无 | 无 | 实现相关接口 | 74 | | 一致性 | 不支持 | 支持 | 最终一致 | 75 | | 隔离性 | 不支持 | 支持 | 业务方保证 | 76 | | 并发性能 | 无影响 | 严重衰退 | 略微衰退 | 77 | | 适合场景 | 业务方处理不一致 | 短事务 & 低并发 | 长事务 & 高并发 | 78 | 79 | ## 参考 80 | 81 | - [柔性事务的定义与分类](https://tech.antfin.com/docs/2/69656) 82 | 83 | - [传统事务与柔性事务](https://www.jianshu.com/p/ab1a1c6b08a1) 84 | 85 | - [分布式架构设计篇(七)-刚性事务总结和柔性事务概述](https://cloud.tencent.com/developer/article/1653146) 86 | 87 | - [分布式架构设计篇(十二)-分布式事务总结篇](https://cloud.tencent.com/developer/article/1659326) 88 | 89 | - [浅谈事务和一致性:刚性 or 柔性?](https://juejin.cn/post/6844903575756210184) 90 | -------------------------------------------------------------------------------- /研发相关/分布式系统/分布式事务/本地消息表.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MyHerux/code-note/854b0f86d319886564c00c21040a1745f88b10a2/研发相关/分布式系统/分布式事务/本地消息表.md -------------------------------------------------------------------------------- /研发相关/分布式系统/分布式系统调用跟踪/Logback.md: -------------------------------------------------------------------------------- 1 | 2 | http://logback.qos.ch/manual/layouts.html 3 | 4 | -------------------------------------------------------------------------------- /研发相关/分布式系统/分布式系统调用跟踪/OpenTracing规范.md: -------------------------------------------------------------------------------- 1 | # OpenTracing规范 2 | **Version: 1.1** 3 | 4 | ## Overview 5 | 6 | `OpenTracing` 致力于为分布式跟踪创建更标准化的 `API` 和工具,它由完整的 `API` 规范、实现该规范的框架、库以及项目文档组成。 7 | `OpenTracing` 提供了一套语言无关、平台无关、厂商无关的 `API` ,这样不同的组织或者开发人员就能够更加方便的添加或更换追踪系统的实现。 `OpenTracingAPI` 中的一些概念和术语,在不同的语言环境下都是共享的。 8 | 9 | ## OpenTracing 数据模型 10 | 11 | `Opentracing` 规范中,一条 `trace` 链路是由多个与之关联的 `span` 组成,一条链路整体可以看做是一张有向无环图,各个 `span` 之间的边缘关系被称之为 `References` 。 12 | 13 | 例如,以下是由 `8` 个 `span` 组成的示例 `trace`: 14 | 15 | >单个 `Trace` 中 `Spans` 之间的因果关系。 16 | 17 | ``` 18 | [Span A] ←←←(the root span) 19 | | 20 | +------+------+ 21 | | | 22 | [Span B] [Span C] ←←←(Span C is a `ChildOf` Span A) 23 | | | 24 | [Span D] +---+-------+ 25 | | | 26 | [Span E] [Span F] >>> [Span G] >>> [Span H] 27 | ↑ 28 | ↑ 29 | ↑ 30 | (Span G `FollowsFrom` Span F) 31 | ``` 32 | 33 | 有时,使用时间轴来可视化跟踪更容易,如下图所示: 34 | 35 | > 单个`Trace` 中 `Spans` 之间的时间关系。 36 | 37 | ``` 38 | ––|–––––––|–––––––|–––––––|–––––––|–––––––|–––––––|–––––––|–> time 39 | 40 | [Span A···················································] 41 | [Span B··············································] 42 | [Span D··········································] 43 | [Span C········································] 44 | [Span E·······] [Span F··] [Span G··] [Span H··] 45 | ``` 46 | 每个 `Span` 封装以下状态: 47 | 48 | - 操作名称 49 | - 开始时间戳 50 | - 结束时间戳 51 | - 一组零个或多个 `key:value` 的 `Span Tags`。`key` 必须是字符串。 `value` 可以是字符串,布尔值或数字类型。 52 | - 一组零个或多个 `Span Logs`,每个 `Span Log` 本身就是与时间戳匹配的 `key:value` 映射。键必须是字符串,尽管值可以是任何类型。但是并非所有 `OpenTracing` 实现都必须支持每种值类型。 53 | - `SpanContext` 54 | - `References` 零个或多个因果相关的 `Spans`(通过那些相关 `Spans` 的 `SpanContext`) 55 | 56 | 每个 `SpanContext` 封装以下状态: 57 | 58 | - 引用跨过程边界的不同 `Span` 所需的任何 `OpenTracing` 实现依赖状态(例如,`trace` 和 `span` ID) 59 | - `Baggage Items`,跨越过程边界的 `key:value` 对。 60 | 61 | ## Spans之间的引用 62 | 63 | `Span` 可以引用因果相关的零个或多个其他 `SpanContext` 。`OpenTracing` 当前定义了两种类型的引用:`ChildOf` 和 `FollowsFrom` 。**两种参考类型都专门为子 `Span` 和父 `Span` 之间的直接因果关系建模。** 将来,OpenTracing可能还会支持具有非因果关系的Span的引用类型(例如,批处理的 `span` ,卡在同一队列中的 `span` 等)。 64 | 65 | `ChildOf` 引用:`Span` 可以是父 `Span` 的 `ChildOf` 。在 `ChildOf` 引用中,父 `Span` 在某种程度上取决于子 `Span` 。以下所有内容将构成 `ChildOf` 关系: 66 | 67 | - 代表`RPC` 的服务器端的 `Span` 可以是代表该 `RPC` 客户端的 `Span` 的 `ChildOf` 68 | - 表示 `SQL` 插入的 `Span` 可以是表示 `ORM` 保存方法的 `Span` 的 `ChildOf` 69 | -------------------------------------------------------------------------------- /研发相关/分布式系统/分布式系统调用跟踪/Sleuth代码解析.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | sleuth: TraceWebServletAutoConfiguration LazyTracingFilter -> brave: TracingFilter 4 | 5 | 6 | 7 | 8 | 9 | ## 参考 10 | 11 | https://www.cnblogs.com/tusheng/articles/10893862.html 12 | 13 | https://juejin.cn/post/6858870430197088264 14 | 15 | https://www.jianshu.com/p/d24ac0be831d -------------------------------------------------------------------------------- /研发相关/分布式系统/分布式系统调用跟踪/一个简单的OpenTracing实现.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## 参考 4 | 5 | - [开放分布式追踪(OpenTracing)入门与 Jaeger 实现](https://zhuanlan.zhihu.com/p/34318538) 6 | 7 | - [OpenTracing中文文档翻译](https://wu-sheng.gitbooks.io/opentracing-io/content/) -------------------------------------------------------------------------------- /研发相关/分布式系统/分布式系统调用跟踪/使用MDC实现日志链路跟踪.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MyHerux/code-note/854b0f86d319886564c00c21040a1745f88b10a2/研发相关/分布式系统/分布式系统调用跟踪/使用MDC实现日志链路跟踪.md -------------------------------------------------------------------------------- /研发相关/分布式系统/分布式系统调用跟踪/分布式链路跟踪.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## 参考 4 | 5 | - [蚂蚁金服分布式链路跟踪组件 SOFATracer 链路透传原理与SLF4J MDC 的扩展能力分析 | 剖析](https://tech.antfin.com/community/articles/272) 6 | 7 | - [微服务分布式架构中,如何实现日志链路跟踪?](https://www.jianshu.com/p/a3ebc9249b69) 8 | 9 | - [分布式系统中如何优雅地追踪日志(原理篇)](https://aijishu.com/a/1060000000083480) -------------------------------------------------------------------------------- /研发相关/区块链/OpenZeppelin集成:编写健壮安全的智能合约.md: -------------------------------------------------------------------------------- 1 | # OpenZeppelin集成:编写健壮安全的智能合约 2 | 3 | 因为智能合约往往涉及金钱,保证Soldity代码没有错误,以及足够的安全是非常根本的。[Zeppelin Solutions](https://zeppelin.solutions/),一个智能合约审查服务商,已经意识到相关的需求。建立在他们的合约审查经验之上,他们把一些最佳实践整理到了[OpenZeppelin](http://truffleframework.com/tutorials/robust-smart-contracts-with-openzeppelin)。 4 | 5 | ## 开箱即用的前端 6 | 7 | 开发的主要精力应该放在智能合约上。为达到这个目的,`Truffle` 以 `truffle box` 的方式提供了拆箱即用的前端。 8 | 9 | - 下载tutorialtoken 10 | 11 | ``` 12 | truffle unbox tutorialtoken 13 | ``` 14 | 15 | - 工程结构 16 | 17 | ![](http://cdn.heroxu.com/20180517152652531971684.png) 18 | 19 | - 集成 `OpenZeppelin` 20 | 21 | ``` 22 | npm install openzeppelin-solidity 23 | ``` 24 | 安装完成之后可以在npm包中看到最新版本的OpenZeppelin: 25 | 26 | ![](http://cdn.heroxu.com/201805171526525843950.png) 27 | 28 | ## 创建TutorialToken智能合约 29 | 30 | 使用已经搭建好的前端,我们可以关注于智能合约本身。 31 | 32 | - 在 `contracts` 目录下,创建名为 `TutorialToken.sol` 的智能合约,内容如下: 33 | 34 | ``` 35 | pragma solidity ^0.4.17; 36 | 37 | import 'openzeppelin-solidity/contracts/token/ERC20/StandardToken.sol'; 38 | 39 | contract TutorialToken is StandardToken { 40 | 41 | } 42 | ``` 43 | 44 | 需要注意: 45 | 46 | - 除了标准的智能合约以外,我们还导入了StandardToken.sol合约并声明我们的TutorialToken 47 | 48 | - is StandardToken 代表继承了StandardToken合约中所有变量和函数。继承的合约可以被覆盖,只要在子类重定义对应的变量与函数就行了。 49 | 50 | - 设置代币的参数,需要定义自己的 `name`, `symbol` , `decimals` 和 `INITIAL_SUPPLY` 51 | 52 | ``` 53 | string public name = 'TutorialToken'; 54 | string public symbol = 'TT'; 55 | uint8 public decimals = 2; 56 | uint public INITIAL_SUPPLY = 12000; 57 | ``` 58 | 59 | - name和symbol给我们的token一个特有的身份 60 | 61 | - decimals定义了token可以细分的程度 62 | 63 | - INITIAL_SUPPLY定义了在合约部署时,代币将创建的数量 64 | 65 | - 在构造函数中我们简单设置 `totalSupply` 来等于 `INITIAL_SUPPLY` ,同时把所有的币赋值给部署者的帐户 66 | 67 | ``` 68 | function TutorialToken() { 69 | totalSupply = INITIAL_SUPPLY; 70 | balances[msg.sender] = INITIAL_SUPPLY; 71 | } 72 | ``` 73 | 74 | ## 编译与部署智能合约 75 | 76 | - 在 `/migrations` 目录下,用下述内容创建文件 `2_deploy_contracts.js`: 77 | 78 | ``` 79 | var TutorialToken = artifacts.require("TutorialToken"); 80 | 81 | module.exports = function(deployer) { 82 | deployer.deploy(TutorialToken); 83 | }; 84 | ``` 85 | 86 | TutorialToken合约内的import语句会由编译器进行自动处理,它会自动导入StandardToken内的相关引用包。 87 | 88 | - 安装本地私链 `Ganache` 89 | 90 | [Truffle框架和Ganache本地私链](https://blog.csdn.net/myherux/article/details/80340095) 91 | 92 | - 编译合约 93 | 94 | ``` 95 | truffle compile 96 | ``` 97 | 98 | - 部署合约到私链上 99 | 100 | ``` 101 | truffle migrate 102 | ``` 103 | 104 | ![](http://cdn.heroxu.com/20180517152652965323652.png) 105 | 106 | 在Ganache上查看交易详情: 107 | 108 | ![](http://cdn.heroxu.com/20180517152652981939829.png) 109 | 110 | ## 与新token交互 111 | 112 | - 前置 113 | 114 | [以太坊轻钱包MetaMask安装](https://blog.csdn.net/MyHerux/article/details/80310595) 115 | 116 | - 本地的简单页面 117 | 118 | ``` 119 | npm run dev 120 | ``` 121 | 122 | ![](http://cdn.heroxu.com/20180517152654075345506.png) 123 | 124 | - 切换 `MetaMask` 到本地网络 125 | 126 | ![](http://cdn.heroxu.com/20180517152654108186265.png) 127 | 128 | - 用私钥导入本地账户 129 | 130 | 导入本地环境的第一个账户(Ganache默认使用的第一个账户) 131 | 132 | ![](http://cdn.heroxu.com/20180517152654128324674.png) 133 | 134 | - 刷新页面,查看代币 135 | 136 | ![](http://cdn.heroxu.com/20180517152654376555130.png) 137 | 138 | - 给第二个账户转2000代币 139 | 140 | ![](http://cdn.heroxu.com/20180517152654395675531.png) 141 | 142 | - 切换到第二个账户,刷新页面,查看token 143 | 144 | ![](http://cdn.heroxu.com/20180517152654414191368.png) -------------------------------------------------------------------------------- /研发相关/区块链/README.md: -------------------------------------------------------------------------------- 1 | ## 区块链 2 | 3 | - [以太坊轻钱包MetaMask安装](./以太坊轻钱包MetaMask安装.md) 4 | 5 | - [使用remix-ide开发以太坊智能合约](./使用remix-ide开发以太坊智能合约.md) 6 | 7 | - [OpenZeppelin集成:编写健壮安全的智能合约](./OpenZeppelin集成:编写健壮安全的智能合约.md) 8 | 9 | - [Truffle框架和Ganache本地私链](./Truffle框架和Ganache本地私链.md) 10 | 11 | - [从0开始完成DApp(一)-基础篇](./从0开始完成DApp(一)-基础篇.md) 12 | 13 | - [从0开始完成DApp(二)-深入篇](./从0开始完成DApp(二)-深入篇.md) 14 | 15 | - [从0开始完成DApp(三)-实践篇](./从0开始完成DApp(三)-实践篇.md) -------------------------------------------------------------------------------- /研发相关/区块链/从0开始完成DApp(一)-基础篇.md: -------------------------------------------------------------------------------- 1 | # 从0开始完成DApp(一)-基础篇 2 | 3 | - 区块链基础 4 | 5 | - [比特币白皮书](http://www.8btc.com/wiki/bitcoin-a-peer-to-peer-electronic-cash-system) 6 | 7 | - [ethereum-overview](http://truffleframework.com/tutorials/ethereum-overview) 8 | 9 | - [以太坊白皮书](https://github.com/ethereum/wiki/wiki/%5B%E4%B8%AD%E6%96%87%5D-%E4%BB%A5%E5%A4%AA%E5%9D%8A%E7%99%BD%E7%9A%AE%E4%B9%A6) 10 | 11 | - [以太坊黄皮书](https://ethereum.github.io/yellowpaper/paper.pdf) 12 | 13 | - Solidity语言 14 | 15 | - [英文官方网站](https://solidity.readthedocs.io/) 16 | 17 | - [代码库GitHub](https://github.com/ethereum/solidity) 18 | 19 | - [solidity中文翻译](http://www.tryblockchain.org/index.html) 20 | 21 | - Truffle框架 22 | 23 | - [英文官方网站](http://truffleframework.com/) 24 | 25 | - [代码库GitHub](https://github.com/trufflesuite/truffle) 26 | 27 | - [truffle中文翻译](http://truffle.tryblockchain.org/) 28 | 29 | - OpenZeppelin 30 | 31 | - [英文官方网站](http://truffleframework.com/tutorials/robust-smart-contracts-with-openzeppelin) 32 | 33 | - [基于OpenZeppelin建立安全加密代币](https://www.jianshu.com/p/40d9e6ea120b) 34 | 35 | - [OpenZeppelin集成Truffle编写健壮安全的合约](http://me.tryblockchain.org/robust-smart-contracts-with-openzeppelin.html) 36 | 37 | - Web3.js 38 | 39 | - [代码库GitHub](https://github.com/ethereum/web3.js) 40 | 41 | - [Web3.js API 中文文档](http://web3.tryblockchain.org/) 42 | 43 | - Metamask 44 | 45 | - [英文官方网站](https://metamask.io/) 46 | 47 | - [以太坊轻钱包MetaMask安装](https://blog.csdn.net/MyHerux/article/details/80310595) -------------------------------------------------------------------------------- /研发相关/区块链/从0开始完成DApp(三)-实践篇.md: -------------------------------------------------------------------------------- 1 | # 从0开始完成DApp(三)-实践篇 2 | 3 | > http://truffleframework.com/tutorials/pet-shop 4 | 5 | ## 项目背景 6 | 7 | Pete想做一个Dapp来卖他的宠物。 8 | 9 | ## 搭建项目环境 10 | 11 | - [Truffle框架和Ganache本地私链](https://blog.csdn.net/myherux/article/details/80340095) 12 | 13 | ## 创建项目 14 | 15 | - 创建项目目录 16 | 17 | ``` 18 | mkdir pet-shop-tutorial 19 | 20 | cd pet-shop-tutorial 21 | ``` 22 | 23 | - 使用 `truffle unbox` 创建项目 24 | 25 | pet-shop项目里面包含一些接本的项目结构和前端代码,方便你专心于智能合约的开发。当然你也可以用 `truffle init` 创建一个空的项目。 26 | 27 | ``` 28 | truffle unbox pet-shop 29 | ``` 30 | 31 | - 项目目录结构 32 | 33 | ![](http://cdn.heroxu.com/20180517152655251941578.png) 34 | 35 | - `contracts/`: 智能合约的文件夹,所有的智能合约文件都放置在这里,里面包含一个重要的合约 `Migrations.sol`。 36 | 37 | - `migrations/`: 用来处理部署智能合约 ,迁移是一个额外特别的合约用来保存合约的变化。 38 | 39 | - `test/`: 智能合约测试用例文件夹。 40 | 41 | - `truffle.js/`: 配置文件。 42 | 43 | ## 编写智能合约 44 | 45 | 智能合约承担着分布式应用的后台逻辑和存储。 46 | 47 | - 在contracts目录下,添加合约文件Adoption.sol 48 | 49 | - 编写合约内容 50 | 51 | ``` 52 | pragma solidity ^0.4.17; 53 | 54 | contract Adoption { 55 | address[16] public adopters; // 保存领养者的地址 56 | // 领养宠物 57 | function adopt(uint petId) public returns (uint) { 58 | require(petId >= 0 && petId <= 15); // 确保id在数组长度内 59 | adopters[petId] = msg.sender; // 保存调用这地址 60 | return petId; 61 | } 62 | // 返回领养者 63 | function getAdopters() public view returns (address[16]) { 64 | return adopters; 65 | } 66 | } 67 | ``` 68 | 69 | - `pragma solidity ^0.4.17` 代表支持 `0.4.17` 版本以上的 `Solidity` 语言 `^` 代表以上 70 | 71 | - Address 72 | 73 | `Solidity` 是一种静态类型语言,意味着必须定义数据类型,如字符串,整数和数组。 `Solidity` 具有称为 `Address` 的独特类型。 `Address` 是以太坊地址,存储为 `20` 个字节的值。以太坊区块链上的每个账户和智能合约都有一个地址,并可以通过此地址发送和接收以太网。 74 | 75 | 76 | -------------------------------------------------------------------------------- /研发相关/区块链/从0开始完成DApp(二)-深入篇.md: -------------------------------------------------------------------------------- 1 | # 从0开始完成DApp(二)-深入篇 2 | 3 | - Step1:安装 `MetaMask` 钱包 4 | 5 | 智能合约的部署需要钱包的参与。 6 | 7 | > [以太坊轻钱包MetaMask安装](https://blog.csdn.net/MyHerux/article/details/80310595) 8 | 9 | - Step2:通过 `Remix` 学习和尝试 `Solidity` 语言 10 | 11 | - `DApp` 的开发语言是 `Solidity` 12 | 13 | - 使用Remix部署智能合约了解整个流程 14 | 15 | > [使用remix-ide开发以太坊智能合约(代币发行)](https://blog.csdn.net/myherux/article/details/80324626) 16 | 17 | - Step3:使用 `Truffle` 框架来开发智能合约 18 | 19 | `Remix` 的开发更多是熟悉智能合约的工作原理和部署流程,实际的智能合约要更复杂的多,所以需要用 `Truffle`框架来管理和简化开发。 20 | 21 | > [Truffle框架和Ganache本地私链](https://blog.csdn.net/myherux/article/details/80340095) 22 | 23 | - Step4:使用OpenZeppelin来保证合约的健壮与安全 24 | 25 | 因为智能合约往往涉及金钱,保证Soldity代码没有错误,以及足够的安全是非常根本的。 26 | 27 | > [OpenZeppelin集成:编写健壮安全的智能合约](https://blog.csdn.net/myherux/article/details/80352470) 28 | 29 | - Step5:实践,开发第一款DApp 30 |  31 | -------------------------------------------------------------------------------- /研发相关/区块链/以太坊轻钱包MetaMask安装.md: -------------------------------------------------------------------------------- 1 | # 以太坊轻钱包MetaMask安装 2 | 3 | ## 基本 4 | 5 | - 官网地址 6 | 7 | > https://metamask.io 8 | 9 | - 特性 10 | 11 | `MetaMask` 是一款在谷歌浏览器 `Chrome` (也支持 `Firefox` 和 `Opera`)上使用的插件类型的以太坊钱包,该钱包不需要下载,非常的轻量级。 12 | 13 | ## 安装教程 14 | 15 | - 安装Chrome 16 | 17 | > https://www.google.com/chrome/ 18 | 19 | - MetaMask插件安装 20 | 21 | - 进入官网,下载插件(需翻墙) 22 | 23 | ![](http://cdn.heroxu.com/20180514152628068830902.png) 24 | 25 | ![](http://cdn.heroxu.com/20180514152628090473344.png) 26 | 27 | ## MetaMask使用 28 | 29 | - 隐私协议 30 | 31 | 点击插件,会先弹出 `隐私协议`: 32 | 33 | > 当登录到MetaMask之后,你的账户对你访问的所有网站都是可见的,为了你的隐私,在使用完MetaMask之后最好退出登录。 34 | 35 | ![](http://cdn.heroxu.com/20180514152628115611863.png) 36 | 37 | - 服务条款 38 | 39 | 需要下滑阅读全文。 40 | 41 | ![](http://cdn.heroxu.com/20180514152628135552855.png) 42 | 43 | - `DEN` 登录 44 | 45 | 导入`DEN`(`DEN` 是在 `MetaMask` 用密码加密存储的钱包): 46 | 47 | ![](http://cdn.heroxu.com/20180514152628152028571.png) 48 | 49 | - 创建新 `DEN` 50 | 51 | ![](http://cdn.heroxu.com/20180514152628251553894.png) 52 | 53 | MetaMask会为用户创建12个英文助记词,一定要保存好这些助记词: 54 | 55 | ![](http://cdn.heroxu.com/20180514152628271225430.png) 56 | 57 | - 进入钱包 58 | 59 | 保存好助记词后就可以进入钱包页面了,MetaMask已经自动为用户创建了一个钱包地址,点击查看更多,分别是: 60 | 61 | - 在Etherscan上查看该钱包地址的所有转帐信息。 62 | 63 | - 显示钱包的二维码。 64 | 65 | - 将钱包地址拷贝到粘贴板。 66 | 67 | - 第四项是导出钱包的私钥(秘钥要保存好,不可丢失或告诉他人)。 68 | 69 | ![](http://cdn.heroxu.com/20180514152628291845705.png) 70 | 71 | - 选择 `Ethereum` 网络(默认主网) 72 | 73 | ![](http://cdn.heroxu.com/20180514152628318377433.png) 74 | 75 | ## Eth交易 76 | 77 | - 购买Eth 78 | 79 | 点击主页的buy即可进入购买页面,MetaMask支持两种购买Eth的方式: 80 | 81 | - Coinbase,是美国第一家持有正规牌照的比特币交易所,可以用美元购买比特币。 82 | 83 | - ShapeShift,是一个无需帐户的数字货币兑换平台,可以将你自己的虚拟货币直接兑换为Eth。 84 | 85 | ![](http://cdn.heroxu.com/20180514152628331877663.png) 86 | 87 | - 发送Eth 88 | 89 | 分别填写 `发送地址`、`金额`、`附加信息`。 90 | 91 | ![](http://cdn.heroxu.com/20180514152628347636318.png) -------------------------------------------------------------------------------- /研发相关/数据库/MySQL/Partitioning/0_分区概述.md: -------------------------------------------------------------------------------- 1 | ## 分区概述 2 | 3 | ### 分区的实现 4 | 5 | Mysql 数据库中的数据是以文件的形式保存在磁盘上的,默认放在 `mysql/data` 路径,不同的引擎生成的文件后缀不同,像 `MyISAM` 生成 `.MYD` 和 `.MYI` 后缀文件, `MYI` 存放索引(非聚簇索引)。`Innodb` 生成 `.idb` 后缀文件(聚簇索引)。当 `mysql` 表中数据越来越多,文件会变的越来越大,这个时候查询数据的速度会变慢。 6 | 7 | ### 分区的好处 8 | 9 | 当表非常大,或者表中有大量的历史记录,而“热数据”却位于表的末尾。如日志系统、新闻。。。此时就可以考虑分区表(也可以使用分表,但是会增加业务的复杂性)。 10 | 11 | 假设一个商城订单系统,每年一个总表为 `order_year_2018` ,最近三个月有一个分表 `order_mouth_3` 。用户按年份选择订单就到年表中查询,按最近三个月选择订单就到最近三个月的分表中查询。此时,使用分区表比设计分表更合适。 12 | 13 | - 存储更多的数据 14 | 15 | 分区使在一个 `table` 中存储的数据比单个磁盘或文件系统分区中存储的数据更多。同时,如果表的数据太大,一个磁盘放不下的时候可以把数据分配到不同的磁盘里面去。 16 | 17 | - 方便删除历史数据 18 | 19 | 通过删除仅包含该数据的一个或多个分区,可以轻松地从分区 `table` 中删除失去其用途的数据。相反,在某些情况下,通过添加一个或多个用于专门存储该数据的新分区,可以大大简化添加新数据的过程。 20 | 21 | 如果是启用一个或多个带 `where` 条件的 `delete` 语句去删除(一般 `where` 条件是时间)。 这对数据库的造成了很大压力。即使我们把这些删除了,但底层的数据文件并没有变小。 22 | 23 | - 提高查询效率 24 | 25 | 可以把一些归类的数据放在一个分区中,可以减少服务器检查数据的数量加快查询。 26 | 这主要是借助于满足一个给定 `WHERE` 语句的数据可以只保存在一个或多个分区内,这样在查找时就不用查找其他剩余的分区。 27 | 28 | 另外, `MySQL` 支持显式的分区选择查询。例如,`SELECT * FROM t PARTITION (p0, p1) WHERE c < 5` 仅选择分区 `p0` 和 `p1` 中与 `WHERE` 条件匹配的那些行。在这种情况下,`MySQL` 不会检查 `table t` 的任何其他分区;当您已经知道要检查的分区时,这可以大大加快查询速度。数据修改语句 `DELETE` , `INSERT` , `REPLACE` , `UPDATE` 和 `LOADDATA` , `LOADXML` 也支持分区选择。 29 | -------------------------------------------------------------------------------- /研发相关/数据库/MySQL/Partitioning/4_分区选择.md: -------------------------------------------------------------------------------- 1 | ## 分区选择 2 | 3 | `MySQL 5.7` 支持显式选择分区和子分区,当执行一条语句时,应检查该分区和子分区中是否存在与给定 `WHERE` 条件匹配的行。分区选择类似于分区修剪,因为只检查特定的分区是否匹配,但是在两个关键方面有所不同: 4 | 5 | - 与要自动执行的分区修剪不同,要检查的分区由语句的发布者指定。 6 | 7 | - 分区修剪仅适用于查询,而查询和许多 `DML` 语句均支持显式选择分区。 8 | 9 | 支持显式分区选择的 SQL 语句: 10 | 11 | - SELECT 12 | 13 | ``` 14 | SELECT * FROM employees PARTITION (p1); 15 | ``` 16 | 17 | - DELETE 18 | 19 | ``` 20 | DELETE FROM employees PARTITION (p0, p1) 21 | WHERE fname LIKE 'j%'; 22 | ``` 23 | 24 | - INSERT 25 | 26 | - REPLACE 27 | 28 | 对于插入行的语句,其行为不同之处在于未能找到合适的分区会导致语句失败。对于 `INSERT` 和 `REPLACE` 语句都是如此,如下所示: 29 | ``` 30 | mysql> INSERT INTO employees PARTITION (p2) VALUES (20, 'Jan', 'Jones', 1, 3); 31 | ERROR 1729 (HY000): Found a row not matching the given partition set 32 | mysql> INSERT INTO employees PARTITION (p3) VALUES (20, 'Jan', 'Jones', 1, 3); 33 | Query OK, 1 row affected (0.07 sec) 34 | 35 | mysql> REPLACE INTO employees PARTITION (p0) VALUES (20, 'Jan', 'Jones', 3, 2); 36 | ERROR 1729 (HY000): Found a row not matching the given partition set 37 | 38 | mysql> REPLACE INTO employees PARTITION (p3) VALUES (20, 'Jan', 'Jones', 3, 2); 39 | Query OK, 2 rows affected (0.09 sec) 40 | ``` 41 | 42 | - UPDATE 43 | 44 | - LOAD DATA. 45 | 46 | - LOAD XML. -------------------------------------------------------------------------------- /研发相关/数据库/MySQL/Partitioning/README.md: -------------------------------------------------------------------------------- 1 | ## Partitioning 2 | 3 | - [0_分区概述](./0_分区概述.md) 4 | 5 | - [1_分区类型](./1_分区类型.md) 6 | 7 | - [2_分区管理](./2_分区管理.md) 8 | 9 | - [3_分区修剪](./3_分区修剪.md) 10 | 11 | - [4_分区选择](./4_分区选择.md) -------------------------------------------------------------------------------- /研发相关/数据库/MySQL/SQL语句/SQL优化详解(三).md: -------------------------------------------------------------------------------- 1 | # MySql 优化详解(三)查询性能优化 2 | 3 | ## 慢查询优化 4 | 5 | #### 优化数据访问 6 | 7 | > 分析方式: 8 | - 确认应用程序是否在检索大量超过需要的数据。 9 | - 确认MySQL服务器层是否在分析大量超过需要的数据行。 10 | 11 | - 是否请求了不需要的数据 12 | 13 | - 查询不需要的记录 14 | - 多表关联时返回全部列 15 | - 总是取出全部列 16 | - 重复查询相同的数据 17 | 18 | - `MySQL` 是否在扫描额外的记录 19 | 20 | > 对于 `MySQL` ,最简单的衡量查询开销的三个指标:`响应时间`,`扫描的行数`,`返回的行数`。这三个指标都会记录到 `MySQL` 的慢 `SQL` 日志中。 21 | 22 | #### 重构查询的方式 23 | 24 | - 一个复杂查询还是多个简单查询 25 | 26 | - 切分查询 27 | 28 | 如果一个大的语句一次性完成,则可能需要一次锁住很多数据、占满整个事务日志。耗尽系统资源、阻塞很多小的但很重要的查询。 29 | 30 | - 分解关联查询 31 | 32 | 优势: 33 | - 让缓存的效率更高 34 | - 将查询分解后,执行单个查询可以减少锁的竞争 35 | - 在应用层做关联,可以更容易对数据库进行拆分,跟容易做到高性能和可扩展 36 | - 查询本身效率也可能会提升 37 | - 减少冗余记录的查询 38 | 39 | #### 查询执行的基础 40 | 41 | MySQL查询过程: 42 | 43 | ![mark](http://of0qa2hzs.bkt.clouddn.com/blog/171229/6dFiAEL71j.png?imageslim) 44 | 45 | - MySQL客户端/服务器通信协议 46 | 47 | MySQL客户端和服务器之间的通信协议是 `半双工` 的,所以,在任何一个时刻,要么是由服务器向客户端发送数据,要么是由客户端向服务器发送数据。因而在实际开发中,尽量保持查询简单且只返回必需的数据,减小通信间数据包的大小和数量是一个非常好的习惯,这也是查询中尽量避免使用 `SELECT *` 以及加上 `LIMIT` 限制的原因之一。 48 | 49 | - 查询状态 50 | > 对于一个MySQL连接,或者说一个线程,任何时刻都有一个状态,该状态表示了MySQL当前正在做什么(SHOW FULL PROCESSLIST)。 51 | - Sleep 52 | 53 | 线程正在等待客户端发送新的请求 54 | - Query 55 | 56 | 线程正在执行查询或者正在将结果发送给客户端 57 | - Locked 58 | 59 | 在MySQL服务器层,该线程正在等待表锁 60 | - Analyzing and statistics 61 | 62 | 线程正在收集存储引擎的统计信息,并生成查询的执行计划。 63 | - Copying to tem table [on disk] 64 | 65 | 线程正在执行查询,并且将其结果集都复制到一个临时表中 66 | 67 | - 查询缓存 68 | 69 | - 查询优化处理 70 | 71 | #### MySQL查询优化器的局限性 72 | 73 | #### 查询优化器的提示 74 | 75 | #### 优化特定类型的查询 -------------------------------------------------------------------------------- /研发相关/数据库/MySQL/日常SQL使用/导出.md: -------------------------------------------------------------------------------- 1 | mysqldump -u root -p DB_Name --no-create-db=TRUE --no-create-info=TRUE --add-drop-table=FALSE --where="id>1000" Table_Name>导出文件名.sql; 2 | -------------------------------------------------------------------------------- /研发相关/数据库/MySQL/日常SQL使用/查看数据库大小.md: -------------------------------------------------------------------------------- 1 | 2 | **查看数据库大小** 3 | 4 | SELECT table_schema "Database Name", sum( data_length + index_length ) / 1024 / 1024/1024 "Database Size in GB" FROM information_schema.TABLES GROUP BY table_schema; 5 | 6 | **查看数据库表大小** 7 | 8 | SELECT CONCAT(table_schema,'.',table_name) AS 'Table Name', CONCAT(ROUND(table_rows/1000000,4),'M') AS 'Number of Rows', CONCAT(ROUND(data_length/(1024*1024*1024),4),'G') AS 'Data Size', CONCAT(ROUND(index_length/(1024*1024*1024),4),'G') AS 'Index Size', CONCAT(ROUND((data_length+index_length)/(1024*1024*1024),4),'G') AS'Total'FROM information_schema.TABLES WHERE table_schema LIKE 'zec'; -------------------------------------------------------------------------------- /研发相关/数据库/Redis/Redis实战应用场景.md: -------------------------------------------------------------------------------- 1 | # 2 | 3 | 4 | ## 朋友圈点赞 5 | 6 | - 点赞 7 | 8 | SADD like:{消息id} {用户id} 9 | 10 | - 取消点赞 11 | 12 | SREM like:{消息id} {用户id} 13 | 14 | - 检查用户是否点过赞 15 | 16 | SISMEMBER like:{消息id} {用户id} 17 | 18 | - 获取点赞的用户列表 19 | 20 | SMEMBERS like:{消息id} 21 | 22 | - 获取点赞用户数 23 | 24 | SCARD like:{消息id} 25 | 26 | 27 | ## 集合操作实现微博微信关注模型 28 | 29 | - zhu关注的人: 30 | 31 | zhuSet -> {a} 32 | 33 | - yang关注的人: 34 | 35 | yangSet -> {a,b,c} 36 | 37 | - zhu和yang的共同关注: 38 | 39 | SINTER zhuSet yangSet -> {a} 40 | 41 | - zhu关注的人也关注yang 42 | 43 | SISMEMBER zhuSet yangSet 44 | 45 | - zhu可能认识的人: 46 | 47 | SDIFF yangSet zhuSet 48 | 49 | ## Zset集合实现排行榜 50 | 51 | - 点击新闻 52 | 53 | ZINCRBY hotnows: -------------------------------------------------------------------------------- /研发相关/系统设计/API管理/README.md: -------------------------------------------------------------------------------- 1 | ## API 管理 2 | 3 | - [使用DockerCompose一键部署Yapi](./Yapi/README.md) -------------------------------------------------------------------------------- /研发相关/系统设计/API管理/Yapi/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:11 as builder 2 | 3 | RUN apt-get install -y git python make openssl tar gcc 4 | ADD yapi.tgz /home/ 5 | RUN mkdir /api && mv /home/yapi-1.12.0 /api/vendors 6 | RUN cd /api/vendors && \ 7 | npm install --production --registry https://registry.npm.taobao.org 8 | 9 | FROM node:11 10 | 11 | MAINTAINER hua.xu 12 | ENV TZ="Asia/Shanghai" HOME="/" 13 | WORKDIR ${HOME} 14 | 15 | COPY --from=builder /api/vendors /api/vendors 16 | COPY config.json /api/ 17 | EXPOSE 3001 18 | 19 | COPY docker-entrypoint.sh /api/ 20 | RUN chmod 755 /api/docker-entrypoint.sh 21 | 22 | ENTRYPOINT ["/api/docker-entrypoint.sh"] -------------------------------------------------------------------------------- /研发相关/系统设计/API管理/Yapi/README.md: -------------------------------------------------------------------------------- 1 | # 使用DockerCompose构建部署Yapi 2 | 3 | ## OverView 4 | 5 | YApi 是一个可本地部署的、打通前后端及QA的、可视化的接口管理平台 https://hellosean1025.github.io/yapi 6 | 7 | ## 准备一个自己的 Mongo 8 | 9 | 因为这些数据都是要落地的,建议单独准备一个DB。 10 | 11 | ## 自己构建镜像 12 | 13 | 自己构建的镜像可以保证镜像的安全,或者可以魔改一下代码再构建镜像。 14 | 15 | - 下载 Yapi 16 | 17 | ``` 18 | wget https://github.com/YMFE/yapi/archive/refs/tags/v1.12.0.tar.gz 19 | ``` 20 | 21 | - 构建镜像 22 | 23 | ``` 24 | docker-compose build 25 | ``` 26 | 27 | - Push 镜像 28 | 29 | ``` 30 | docker tag skycitygalaxy/yapi:latest skycitygalaxy/yapi:v1.12.0 31 | docker push skycitygalaxy/yapi:v1.12.0 32 | ``` 33 | 34 | ## 直接使用镜像,本地部署 35 | 36 | 如果不想自己构建镜像的话,可以使用我打包好的镜像:skycitygalaxy/yapi:v1.12.0 37 | 38 | - 拉取镜像 39 | 40 | ``` 41 | docker pull skycitygalaxy/yapi:v1.12.0 42 | ``` 43 | 44 | - 启动服务 45 | 46 | ``` 47 | docker run -d -p 3001:3000 --name yapi skycitygalaxy/yapi:v1.12.0 48 | ``` 49 | 50 | - 修改配置 51 | 52 | 进入容器,修改配置为自己的配置。 53 | 54 | ``` 55 | docker exec -ti yapi bash 56 | cd /api/ 57 | vim config.json 58 | ``` 59 | 60 | - 重启服务 61 | 62 | ``` 63 | docker restart yapi 64 | ``` 65 | 66 | - 访问 http://127.0.0.1:3001/ 67 | 68 | ![](http://cdn.heroxu.com/2019080815652468118063.png) 69 | 70 | ## 使用 Rancher 部署 71 | 72 | - 配置环境变量 73 | 74 | ![](http://cdn.heroxu.com/20190808156524572847385.png) 75 | 76 | - 部署完成 77 | 78 | ![](http://cdn.heroxu.com/20190808156524588021590.png) 79 | 80 | ![](http://cdn.heroxu.com/20190808156524581769215.png) 81 | -------------------------------------------------------------------------------- /研发相关/系统设计/API管理/Yapi/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "port": "MY_PORT", 3 | "adminAccount": "MY_ACOUNT", 4 | "db": { 5 | "servername": "MY_DB_SERVER", 6 | "DATABASE": "MY_DB_NAME", 7 | "port": "MY_DB_PORT", 8 | "user": "MY_USER", 9 | "pass": "MY_PASS", 10 | "authSource": "MY_AUTH" 11 | } 12 | } -------------------------------------------------------------------------------- /研发相关/系统设计/API管理/Yapi/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.7' 2 | networks: 3 | netdci: 4 | 5 | services: 6 | yapi: 7 | build: 8 | context: ./ 9 | dockerfile: ./Dockerfile 10 | image: skycitygalaxy/yapi 11 | container_name: yapi 12 | environment: 13 | - VERSION=v1.12.0 14 | - LOG_PATH=/tmp/yapi.log 15 | - HOME=/home 16 | - MY_PORT=3001 17 | - MY_ACOUNT=heroxu123@gmail.com 18 | - MY_DB_SERVER=127.0.0.1 19 | - MY_DB_NAME=yapi 20 | - MY_DB_PORT=27027 21 | - MY_USER=xu 22 | - MY_PASS=xu 23 | - MY_AUTH=test 24 | ports: 25 | - 127.0.0.1:3000:3000 26 | volumes: 27 | - ~/data/yapi/log/yapi.log:/home/vendors/log # log dir 28 | depends_on: 29 | - mongo 30 | networks: 31 | - netdci 32 | 33 | mongo: 34 | image: mongo 35 | container_name: mongo 36 | ports: 37 | - 127.0.0.1:27027:27017 38 | volumes: 39 | - ~/data/yapi/mongodb:/data/db #db dir 40 | networks: 41 | - netdci 42 | -------------------------------------------------------------------------------- /研发相关/系统设计/API管理/Yapi/docker-entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -eo pipefail 3 | shopt -s nullglob 4 | 5 | MY_PORT="${MY_PORT:=3000}" 6 | MY_ACOUNT="${MY_ACOUNT:=heroxu123@gmail.com}" 7 | MY_DB_SERVER="${MY_DB_SERVER:=127.0.0.1}" 8 | MY_DB_NAME="${MY_DB_NAME:=yapi}" 9 | MY_DB_PORT="${MY_DB_PORT:=27027}" 10 | MY_USER="${MY_USER:=xu}" 11 | MY_PASS="${MY_PASS:=xu}" 12 | MY_AUTH="${MY_AUTH:=test}" 13 | 14 | config() { 15 | if [[ -z "${MY_PORT}" || -z "${MY_ACOUNT}" || -z "${MY_DB_SERVER}" || -z "${MY_DB_NAME}" || -z "${MY_DB_PORT}" || -z "${MY_USER}" || -z "${MY_PASS}" || -z "${MY_AUTH}" ]]; then 16 | echo -e "\n\"MY_PORT\" or \"MY_ACOUNT\" or \"MY_DB_SERVER\" or \"MY_DB_NAME\" or \"MY_DB_PORT\" or \"MY_USER\" or \"MY_PASS\" or \"MY_AUTH\" can not be empty!\n" && exit 1 17 | else 18 | sed -i "s#MY_PORT#${MY_PORT}#g" /api/config.json 19 | sed -i "s#MY_ACOUNT#${MY_ACOUNT}#g" /api/config.json 20 | sed -i "s#MY_DB_SERVER#${MY_DB_SERVER}#g" /api/config.json 21 | sed -i "s#MY_DB_NAME#${MY_DB_NAME}#g" /api/config.json 22 | sed -i "s#MY_DB_PORT#${MY_DB_PORT}#g" /api/config.json 23 | sed -i "s#MY_USER#${MY_USER}#g" /api/config.json 24 | sed -i "s#MY_PASS#${MY_PASS}#g" /api/config.json 25 | sed -i "s#MY_AUTH#${MY_AUTH}#g" /api/config.json 26 | 27 | fi 28 | } 29 | 30 | config 31 | 32 | node /api/vendors/server/app.js 33 | 34 | exec "$@" -------------------------------------------------------------------------------- /研发相关/系统设计/API管理/Yapi/download.sh: -------------------------------------------------------------------------------- 1 | function usage(){ 2 | echo "usage: sh build.sh " 3 | echo "默认版本: 1.7.1" 4 | echo "yapi的版本: https://github.com/YMFE/yapi/releases" 5 | echo "我们将从这里下载: http://registry.npm.taobao.org/yapi-vendor/download/yapi-vendor-\$1.tgz" 6 | } 7 | 8 | 9 | 10 | version=1.7.1 11 | 12 | if [ -n "$1" ]; then 13 | version=$1 14 | fi 15 | 16 | usage 17 | 18 | 19 | echo -e "\033[32m download new package (version $version) \033[0m" 20 | 21 | wget -O yapi.tgz http://registry.npm.taobao.org/yapi-vendor/download/yapi-vendor-$version.tgz --no-check-certificate 22 | -------------------------------------------------------------------------------- /研发相关/系统设计/API管理/Yapi/yapi.tgz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MyHerux/code-note/854b0f86d319886564c00c21040a1745f88b10a2/研发相关/系统设计/API管理/Yapi/yapi.tgz -------------------------------------------------------------------------------- /研发相关/系统设计/CAS/README.md: -------------------------------------------------------------------------------- 1 | ## CAS 2 | 3 | - [SpringBoot-CAS在前后端分离中的实践](./SpringBoot-CAS在前后端分离中的实践.md) 4 | -------------------------------------------------------------------------------- /研发相关/系统设计/CAS/SpringBoot-CAS在前后端分离中的实践.md: -------------------------------------------------------------------------------- 1 | ## Cas 2 | 3 | CAS is an open and well-documented authentication protocol. The primary implementation of the protocol is an open-source Java server component by the same name hosted here, with support for a plethora of additional authentication protocols and features. 4 | 5 | 项目地址: 6 | 7 | [https://github.com/apereo/cas](https://github.com/apereo/cas) 8 | 9 | 协议过程: 10 | 11 | ![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly9jZG4uaGVyb3h1LmNvbS8yMDE5MDQyNDE1NTYwOTA0NzcyMjIyOS5wbmc?x-oss-process=image/format,png) 12 | 13 | ## Cas-server 14 | 15 | 直接使用template搭建一个Server: 16 | [https://github.com/apereo/cas-overlay-template](https://github.com/apereo/cas-overlay-template) 17 | 18 | ## 在SpringBoot中集成Cas-client 19 | 20 | 依赖: 21 | 22 | ``` 23 | 24 | net.unicon.cas 25 | cas-client-autoconfig-support 26 | 1.7.0-GA 27 | 28 | ``` 29 | 30 | application.yml: 31 | 32 | ``` 33 | cas: 34 | server-url-prefix: http://server-url 35 | server-login-url: http://server-url/login 36 | client-host-url: http://localhost:9100 37 | validation-type: cas 38 | authentication-url-patterns: 39 | 40 | server: 41 | port: 9100 42 | ``` 43 | 44 | [项目地址](https://github.com/MyHerux/cases/tree/master/cases-cas) 45 | 46 | ## Cas在前后端分离项目中遇到的问题 47 | 48 | 对于访问后端的请求分为两种: 49 | 50 | - HTTP请求 51 | 52 | 像浏览器地址栏发起的请求、浏览器自发的访问某个网址、Postman测试接口,这些行为其实都是发起的HTTP请求,不会有跨域问题。最开始后端测试的时候发起的都是此类请求,所以没有问题,结果前后端联调时就出现了问题。 53 | 54 | - AJAX(XMLHttpRequest)请求 55 | 56 | 这是浏览器内部的 `XMLHttpRequest` 对象发起的请求,浏览器会禁止其发起跨域的请求,主要是为了防止跨站脚本伪造的攻击(`CSRF`)。 57 | 58 | 所以即使已经登录(浏览器有对应的 `cookie` ),前后端分离时( `cookie` 的 `domain` 不一致),前端发起的所有请求都会收到 `302` 错误码。 59 | 60 | ## 前后端分离中的实践-Nginx代理 61 | 62 | 既然是因为 `cookie` 的 `domain` 不一致才导致的 `302` ,直接让 `domain` 一致不就可以了么,首先想到的就是使用 `Nginx` 。 63 | 64 | `nginx.conf` 配置如下: 65 | 66 | ``` 67 | #user nobody; 68 | worker_processes 1; 69 | 70 | #error_log logs/error.log; 71 | #error_log logs/error.log notice; 72 | #error_log logs/error.log info; 73 | 74 | #pid logs/nginx.pid; 75 | 76 | 77 | events { 78 | worker_connections 1024; 79 | } 80 | 81 | 82 | http { 83 | include mime.types; 84 | default_type application/octet-stream; 85 | 86 | #access_log logs/access.log main; 87 | 88 | sendfile on; 89 | #tcp_nopush on; 90 | 91 | #keepalive_timeout 0; 92 | keepalive_timeout 65; 93 | 94 | #gzip on; 95 | 96 | server { 97 | listen 8070; 98 | server_name [server-ip]; 99 | 100 | location /api/ { 101 | proxy_set_header Host $host; 102 | proxy_set_header X-Real-IP $remote_addr; 103 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 104 | proxy_pass http://[server-ip]:8080/api/; 105 | } 106 | location / { 107 | proxy_set_header Host $host; 108 | proxy_set_header X-Real-IP $remote_addr; 109 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 110 | proxy_pass http://[front-ip]:8088/; 111 | } 112 | } 113 | 114 | include servers/*; 115 | } 116 | ``` 117 | 118 | 如配置所示,统一通过入口 `http://[server-ip]:8070` 进入,然后请求会转发到 `http://[front-ip]:8088/`(即前端页面),`http://[front-ip]:8088/`(前端页面)里面的所有请求指向 `http://[server-ip]:8070/api`,这些请求会再被转发到 `http://[server-ip]:8080/api/` (`注意端口的变化`) 119 | 120 | ## 前后端分离中的实践-Token化 121 | 122 | 使用 `Token` 来鉴权当然是前后端分离的最佳选择,但是老旧系统里面的 `cas` 授权又不能去掉,所以只能让新系统里面的 `cas` 来转换为 `token` 。 -------------------------------------------------------------------------------- /研发相关/系统设计/IDE/README.md: -------------------------------------------------------------------------------- 1 | ## IDE 2 | 3 | - [使用IDEA自带的Editor-REST-Client来测试REST-API](./使用IDEA自带的Editor-REST-Client来测试REST-API.md) -------------------------------------------------------------------------------- /研发相关/系统设计/IDE/使用IDEA自带的Editor-REST-Client来测试REST-API.md: -------------------------------------------------------------------------------- 1 | # 使用IDEA自带的 Editor REST Client 来测试 REST API 2 | 3 | ## Overview 4 | 5 | 开发 REST API 的时候,必然少不了测试。测试 API 可以采用以下方式: 6 | 7 | - Chrome 请求 8 | 9 | 不方便构造 POST 请求 10 | 11 | - Postman 等 Post 工具ß 12 | 13 | 需要下载工具,好处是可以记录请求,批量测试 14 | 15 | - Swagger 16 | 17 | 项目集成,方便操作,同时也提供给前端使用 18 | 19 | - Editor REST Client 20 | 21 | IDEA自带,操作方便,不需要要切换到浏览器页面,所以开发的时候测试使用特别方便 22 | 23 | ## Editor REST Client 24 | 25 | - 集成 26 | 27 | IDEA 2017.1 之后默认集成 Editor REST Client 插件,只需要启动项目即可。 28 | 29 | ![](http://cdn.heroxu.com/20180613152886003275704.png) 30 | 31 | - 测试 32 | 33 | 点击 `Run HTTP Request` 按钮即可开始测试,GET请求多一个按钮可以在浏览器中打开资源或页面。 34 | 35 | - POST请求 36 | 37 | ![](http://cdn.heroxu.com/20180613152885997427551.png) 38 | 39 | - GET请求 40 | 41 | ![](http://cdn.heroxu.com/20180613152886030584590.png) 42 | 43 | - 配置 44 | 45 | 点击 `Open in HTTP Request Editer` 进行配置 46 | 47 | ![](http://cdn.heroxu.com/20180613152886075540719.png) 48 | 49 | 50 | ## 官方描述 51 | 52 | > https://www.jetbrains.com/help/idea/http-client-in-product-code-editor.html 53 | 54 | 55 | -------------------------------------------------------------------------------- /研发相关/系统设计/好玩的/VSCode插件发布.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: %s 3 | tags: %s 4 | keywords: 帐户,的 5 | --- 6 | # VSCode插件发布 7 | 8 | ## 获取 Personal Access Token(PAT) 9 | 10 | - 注册账号 11 | 12 | [Visual Studio Team Services](https://www.visualstudio.com/zh-hans/team-services/?rr=https%3A%2F%2Fdocs.microsoft.com%2Fzh-cn%2Fvsts%2Faccounts%2Fcreate-account-msa-or-work-student) 13 | 14 | - 创建 `Visual Studio Team Services` 的的帐户户后 15 | 16 | ![mark](http://of0qa2hzs.bkt.clouddn.com/blog/171130/GaDHKgbDF4.png?imageslim) 17 | 18 | - 创建PAT 19 | 20 | ![mark](http://of0qa2hzs.bkt.clouddn.com/blog/171130/3mbm1E371H.png?imageslim) 21 | 22 | ![mark](http://of0qa2hzs.bkt.clouddn.com/blog/171130/5J5IiCebb5.png?imageslim) 23 | 24 | ![mark](http://of0qa2hzs.bkt.clouddn.com/blog/171130/6IBHl3h86A.png?imageslim) 25 | 26 | 下一个屏幕将显示您新创建的个人访问令牌。复制并保持(密码不会被 `Visual Studio Team Services` 永久保存,需要自己保存),你将需要它来创建一个发布者。 27 | 28 | ## 创建发布者 29 | 30 | - 创建新的发布者 31 | 32 | > vsce create-publisher (publisher name) 33 | 34 | - 登录发布者账号 35 | 36 | > vsce create-publisher (publisher name) 37 | 38 | ## 发布插件 39 | 40 | - 安装 `vsce` 41 | 42 | > npm install -g vsce 43 | 44 | - 使用 `vsce` 发布插件 45 | 46 | ``` 47 | $ vsce publish 48 | Publishing uuid@0.0.1... 49 | Successfully published uuid@0.0.1! 50 | ``` -------------------------------------------------------------------------------- /研发相关/系统设计/好玩的/使用Selenium破解拼图验证码.md: -------------------------------------------------------------------------------- 1 | # 使用Selenium破解拼图验证码 2 | 3 | ## 工具准备 4 | 5 | - [官网](http://www.seleniumhq.org/) 6 | 7 | - python 安装 8 | 9 | > pip install -U selenium 10 | 11 | or 12 | 13 | > python setup.py install 14 | 15 | - 下载 chromedriver 16 | 17 | - [官网](http://chromedriver.storage.googleapis.com/index.html) 18 | 19 | - [chromedriver与chrome版本映射表](http://blog.csdn.net/huilan_same/article/details/51896672) 20 | 21 | ## 编码 22 | 23 | - 使用 `Chromedriver` 打开目标网站 24 | 25 | ``` 26 | path = ".\chromedriver.exe" 27 | driver = webdriver.Chrome(executable_path=path) 28 | url = "https://account.geetest.com/register" 29 | driver.get(url) 30 | time.sleep(5) 31 | ``` 32 | - -------------------------------------------------------------------------------- /研发相关/系统设计/好玩的/使用函数计算来构建小程序.md: -------------------------------------------------------------------------------- 1 | # 使用函数计算来构建小程序 2 | 3 | ## 传统服务器架构 VS Serverless架构 4 | 5 | ![mark](http://of0qa2hzs.bkt.clouddn.com/blog/180312/7l9hGf6HLk.png?imageslim) 6 | 7 | ## Serverless架构 8 | 9 | ![mark](http://of0qa2hzs.bkt.clouddn.com/blog/180312/Jag01abBfE.png?imageslim) 10 | 11 | - Login & Auth 12 | 13 | > [微信登录状态维护](https://mp.weixin.qq.com/debug/wxadoc/dev/api/api-login.html#wxchecksessionobject) 14 | 15 | ![mark](http://of0qa2hzs.bkt.clouddn.com/blog/180313/liBcaKc6Cf.png?imageslim) 16 | 17 | - 语音识别 18 | 19 | - 音频格式转换 20 | 21 | > 使用ffpmeg来实现音频转换 22 | 23 | ``` 24 | //使用ffmpeg将mp3转化为wav 25 | String bashCommand = String.format("./ffmpeg -y -i %s %s", mp3Filename, wavFilename); 26 | String lsCommand = "ls"; 27 | Runtime runtime = Runtime.getRuntime(); 28 | commandExec(lsCommand, runtime); 29 | commandExec(bashCommand, runtime); 30 | ``` 31 | 32 | - 百度语音识别 33 | 34 | 文档地址:[百度语音识别](https://ai.baidu.com/docs?spm=a2c4e.11153959.blogcont435430.24.74da48d1SwA4hQ#/ASR-Online-Python-SDK/top) 35 | 36 | Java实现: 37 | 38 | ``` 39 | /** 40 | * 调用百度语音接口 41 | * 42 | * @param path 语音文件路径 43 | * @param format 文件格式 44 | * @return 语音识别结果 45 | */ 46 | private static JSONObject getSpeechResult(String path, String format) { 47 | // 初始化一个AipSpeech 48 | AipSpeech client = new AipSpeech(APP_ID, API_KEY, SECRET_KEY); 49 | // 可选:设置网络连接参数 50 | client.setConnectionTimeoutInMillis(2000); 51 | client.setSocketTimeoutInMillis(60000); 52 | 53 | HashMap options = new HashMap<>(2); 54 | options.put("lan", "zh"); 55 | // 调用接口 56 | return JSONObject.parseObject(client.asr(path, format, 16000, options).toString()); 57 | } 58 | ``` 59 | 60 | ## 函数计算 61 | 62 | - 开通阿里云函数计算服务(当然腾讯云也有一样的功能) 63 | 64 | - 购买域名,备案,申请ssl证书(又拍云免费) 65 | 66 | - 开通 [微信小程序](https://mp.weixin.qq.com/debug/wxadoc/dev/?spm=a2c4e.11153959.blogcont435430.31.74da48d1SwA4hQ) 开发认证,以及 [百度语音识别](http://yuyin.baidu.com/?spm=a2c4e.11153959.blogcont435430.32.74da48d1SwA4hQ) 67 | 68 | - 创建服务端 69 | 70 | - 登录状态维护接口 71 | 72 | - 语言文字转换接口 73 | 74 | - 使用 `fcli` 上传函数 75 | 76 | > 建议使用 `Linux` 环境 77 | 78 | - [fcli地址](https://github.com/aliyun/fcli/releases?spm=a2c4e.11153959.blogcont435430.35.665248d1AjTmuj) 79 | 80 | - 在 `fcli` 可执行文件所在的文件夹下,`./fcli shell` 进入交互模式。第一次使用需要输入配置信息。 81 | 82 | - mks myService 83 | 84 | > 新建一个服务,不带任何高级配置内容 85 | 86 | - cd myService 87 | 88 | - mkf myFunction -h com.xu.t3.HelloFC::handleRequest -d E:\\test -t java8 89 | 90 | > 新建一个函数,-h指定函数入口,-d指定了代码所在目录,-t指定runtime 91 | 92 | 93 | - 以函数计算作为 API 网关后端服务 94 | 95 | - 创建微信小程序 -------------------------------------------------------------------------------- /研发相关/系统设计/微服务/README.md: -------------------------------------------------------------------------------- 1 | ## 微服务 2 | 3 | - [0_调用链_Zipkin](./0_调用链_Zipkin.md) -------------------------------------------------------------------------------- /研发相关/系统设计/日志分析/README.md: -------------------------------------------------------------------------------- 1 | ## 日志分析 2 | 3 | - [Elasticsearch+Logstash+kibana搭建可视化日志分析平台](./Elasticsearch+Logstash+kibana搭建可视化日志分析平台.md) -------------------------------------------------------------------------------- /研发相关/系统设计/流量治理/设计一个基于用户的限流策略.md: -------------------------------------------------------------------------------- 1 | # 设计一个基于用户的API限流策略 Rate Limit 2 | 3 | ## 应用场景 4 | 5 | API接口的流量控制策略:缓存、降级、限流。限流可以认为服务降级的一种,限流就是限制系统的输入和输出流量已达到保护系统的目的。限流策略虽然降低了服务接口的访问频率和并发量,却换取服务接口和业务应用系统的高可用。 6 | 7 | 常用的限流策略: 8 | 9 | - `Nginx` 限流 10 | 11 | 按照一定的规则如帐号、IP、系统间调用逻辑等在 `Nginx` 层面做限流 12 | 13 | - 业务系统限流 14 | 15 | - 客户端限流 16 | 17 | - 服务端限流 18 | 19 | - 数据库限流 20 | 21 | ## 常用限流算法 22 | 23 | - 计数器 24 | 25 | 计数器是最简单粗暴的算法,通过直接统计每个时间的请求数目来判断是否需要拒绝。 26 | 27 | 比如某个服务最多只能每秒钟处理 `100` 个请求。我们可以设置一个 `1` 秒钟的滑动窗口,窗口中有 `10` 个格子,每个格子 `100` 毫秒,每 `100` 毫秒移动一次,每次移动都需要记录当前服务请求的次数。内存中需要保存 `10` 次的次数。可以用数据结构 `LinkedList` 来实现。格子每次移动的时候判断一次,当前访问次数和 `LinkedList` 中最后一个相差是否超过 `100` ,如果超过就需要限流了。 28 | 29 | ![mark](http://of0qa2hzs.bkt.clouddn.com/blog/180119/a5J5K65dFB.png?imageslim) 30 | 31 | 很明显,当滑动窗口的格子划分的越多,那么滑动窗口的滚动就越平滑,限流的统计就会越精确。 32 | > 计数器的实现简单,但是是平均分配1秒钟的请求,然而实际情况中的请求往往是动态的,流量不平滑的。 33 | 34 | - 漏桶 35 | 36 | 漏桶( `Leaky Bucket` )算法思路很简单,水(请求)先进入到漏桶里,漏桶以一定的速度出水(接口有响应速率),当水流入速度过大会直接溢出(访问频率超过接口响应速率),然后就拒绝请求,可以看出漏桶算法能强行限制数据的传输速率。 37 | 38 | ![mark](http://of0qa2hzs.bkt.clouddn.com/blog/180119/IA0F7ad2FK.png?imageslim) 39 | 40 | 可见这里有两个变量,一个是桶的大小,支持流量突发增多时可以存多少的水( `burst` ),另一个是水桶漏洞的大小( `rate` )。 41 | 42 | 因为漏桶的漏出速率是固定的参数,所以,即使网络中不存在资源冲突(没有发生拥塞),漏桶算法也不能使流突发( `burst` )到端口速率.因此,漏桶算法对于存在突发特性的流量来说缺乏效率. 43 | 44 | - 令牌桶 45 | 46 | 令牌桶算法( `Token Bucket` )和 Leaky Bucket 效果一样但方向相反的算法,更加容易理解.随着时间流逝,系统会按恒定 `1/QPS` 时间间隔(如果 `QPS=100` ,则间隔是 `10ms` )往桶里加入 `Token`(想象和漏洞漏水相反,有个水龙头在不断的加水),如果桶已经满了就不再加了.新请求来临时,会各自拿走一个 `Token` ,如果没有 `Token` 可拿了就阻塞或者拒绝服务. 47 | 48 | ![mark](http://of0qa2hzs.bkt.clouddn.com/blog/180119/I75laa3e6I.png?imageslim) 49 | 50 | 令牌桶的另外一个好处是可以方便的改变速度. 一旦需要提高速率,则按需提高放入桶中的令牌的速率. 一般会定时(比如100毫秒)往桶中增加一定数量的令牌, 有些变种算法则实时的计算应该增加的令牌的数量. 51 | 52 | ## 基于Redis的令牌桶算法限流策略实现 53 | 54 | - 策略 55 | 56 | 因为出现了某些客户突然加大流量的情况,为了避免抢占其他用户的资源,所以设计了基于用户(限制单个用户的最大请求数)的令牌桶策略。 57 | 58 | - JAVA 实现 59 | 60 | ``` 61 | /** 62 | * 获取令牌 63 | * 64 | * @param key 令牌类别标识(每个用户不同) 65 | * @param permits 请求的令牌数量 66 | * @param currMillSecond 当前毫秒数 67 | * @return 是否能请求到令牌 68 | */ 69 | public boolean acquire(String key, Integer permits, long currMillSecond) { 70 | try (Jedis jedis = JedisPoolUtil.getJedisPool().getResource()) { 71 | //针对新用户创建令牌桶 72 | if (!jedis.exists(key)) { 73 | jedis.hset(key, "last_mill_second", String.valueOf(currMillSecond)); 74 | jedis.hset(key, "curr_permits", "0"); 75 | jedis.hset(key, "max_permits", "500"); 76 | jedis.hset(key, "rate", "400"); 77 | return true; 78 | } 79 | //获取令牌桶信息,上一个令牌时间,当前可用令牌数,最大令牌数,令牌消耗速率 80 | List limitInfo = jedis.hmget(key, "last_mill_second", "curr_permits", "max_permits", "rate"); 81 | long lastMillSecond = Long.parseLong(limitInfo.get(0)); 82 | Integer currPermits = Integer.valueOf(limitInfo.get(1)); 83 | Integer maxPermits = Integer.valueOf(limitInfo.get(2)); 84 | Double rate = Double.valueOf(limitInfo.get(3)); 85 | //向桶里面添加令牌 86 | Double reversePermitsDouble = ((currMillSecond - lastMillSecond) / 1000) * rate; 87 | Integer reversePermits = reversePermitsDouble.intValue(); 88 | Integer expectCurrPermits = reversePermits + currPermits; 89 | Integer localCurrPermits = Math.min(expectCurrPermits, maxPermits); 90 | //添加令牌之后更新时间 91 | if (reversePermits > 0) { 92 | jedis.hset(key, "last_mill_second", String.valueOf(currMillSecond)); 93 | } 94 | //判断桶里面剩余的令牌数目 95 | if (localCurrPermits - permits >= 0) { 96 | jedis.hset(key, "curr_permits", String.valueOf(localCurrPermits - permits)); 97 | return true; 98 | } else { 99 | jedis.hset(key, "curr_permits", String.valueOf(localCurrPermits)); 100 | return false; 101 | } 102 | } catch (Exception e) { 103 | return false; 104 | } 105 | } 106 | ``` 107 | 108 | 使用方式: 109 | ``` 110 | if (!rateLimiterService.acquire("limiter:" + uid, 1, System.currentTimeMillis())) { 111 | throw new BusinessException(ExceptionType.TOO_BUSY); 112 | } 113 | ``` -------------------------------------------------------------------------------- /研发相关/系统设计/秒杀系统/ESI与CSI.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MyHerux/code-note/854b0f86d319886564c00c21040a1745f88b10a2/研发相关/系统设计/秒杀系统/ESI与CSI.md -------------------------------------------------------------------------------- /研发相关/系统设计/秒杀系统/什么是CDN.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MyHerux/code-note/854b0f86d319886564c00c21040a1745f88b10a2/研发相关/系统设计/秒杀系统/什么是CDN.md -------------------------------------------------------------------------------- /研发相关/系统设计/缓存/README.md: -------------------------------------------------------------------------------- 1 | ## 缓存 2 | 3 | - [缓存之算法探究与实现](./缓存之算法探究与实现.md) 4 | 5 | - [缓存之Caffeine](./缓存之Caffeine.md) 6 | 7 | - [缓存之GuavaCache](./缓存之GuavaCache.md) 8 | 9 | - [缓存之LFU算法](./缓存之LFU算法.md) 10 | 11 | - [深入浅出分布式缓存的通用方法](./深入浅出分布式缓存的通用方法.md) 12 | 13 | - [SpringBoot缓存之Caffeine](./SpringBoot缓存之Caffeine.md) -------------------------------------------------------------------------------- /研发相关/系统设计/缓存/SpringBoot缓存之Caffeine.md: -------------------------------------------------------------------------------- 1 | ## Springboot 对缓存的支持 2 | 3 | Spring Framework 支持透明地向应用程序添加缓存。从本质上讲,抽象将缓存应用于方法,从而根据缓存中可用的信息减少执行次数。缓存逻辑是透明应用的,不会对调用者造成任何干扰。只要通过 `@EnableCaching` 批注启用了缓存支持,Spring Boot 就会自动配置缓存基础结构。 4 | 5 | 比如: 6 | 7 | ``` 8 | import org.springframework.cache.annotation.Cacheable; 9 | import org.springframework.stereotype.Component; 10 | 11 | @Component 12 | public class MathService { 13 | 14 | @Cacheable("piDecimals") 15 | public int computePiDecimal(int i) { 16 | // ... 17 | } 18 | 19 | } 20 | ``` 21 | 22 | ## 缓存的实现 23 | 24 | 缓存抽象不提供实际存储,而是依赖于 `org.springframework.cache.Cache` 和`org.springframework.cache.CacheManager` 接口实现的抽象。 25 | 26 | ![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly9jZG4uaGVyb3h1LmNvbS8yMDE5MDQxMTE1NTQ5NTQ0NTc2ODU1MS5wbmc?x-oss-process=image/format,png) 27 | 28 | 如果您尚未定义 `CacheManager` 类型的 `bean` 或名为 `cacheResolver` 的 `CacheResolver`,则 Spring Boot 会尝试检测以下提供程序(按指示的顺序): 29 | 30 | 1. Generic 31 | 2. JCache (JSR-107) (EhCache 3, Hazelcast, Infinispan, and others) 32 | 3. EhCache 2.x 33 | 4. Hazelcast 34 | 5. Infinispan 35 | 6. Couchbase 36 | 7. Redis 37 | 8. Caffeine 38 | 9. Simple 39 | 40 | ## Caffeine 41 | 42 | [Caffeine](https://github.com/ben-manes/caffeine) 是 Java 8重写的 Guava 缓存,取代了对 Guava 的支持。如果存在 Caffeine,则会自动配置 `CaffeineCacheManager`(由 `spring-boot-starter-cache` “Starter”提供) 43 | 44 | ![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly9jZG4uaGVyb3h1LmNvbS8yMDE5MDQxMTE1NTQ5NTQ2MjE2NDQ0NC5wbmc?x-oss-process=image/format,png) 45 | 46 | ## 定义 CacheManager 来管理缓存 47 | 48 | 由上文可知,当存在 `CacheManager` 类型的 `bean` 时,Spring Boot 会优先使用应用定义的 CacheManager。 49 | 50 | > 本例只使用了 Caffeine 来作为缓存,实际可以定义多种不同的缓存,通过不同的 cacheNames 来使用 51 | 52 | ``` 53 | @Configuration 54 | @EnableCaching 55 | public class LocalCacheConfig { 56 | 57 | private static final int DEFAULT_MAXSIZE = 1000; 58 | private static final int DEFAULT_TTL = 1; 59 | 60 | /** 61 | * 定义不同的cache名称、超时市场(秒)、最大容量。 62 | * 63 | * 每个cache缺省:1秒超时、最多缓存1000条数据,需要修改可以在构造方法的参数中指定。 64 | */ 65 | public enum Caches { 66 | TEST_A(10, 10), 67 | TEST_B(10 * 6, 100); 68 | 69 | 70 | Caches() { 71 | } 72 | 73 | Caches(int ttl) { 74 | this.ttl = ttl; 75 | } 76 | 77 | Caches(int ttl, int maxSize) { 78 | this.ttl = ttl; 79 | this.maxSize = maxSize; 80 | } 81 | 82 | private int maxSize = DEFAULT_MAXSIZE; //最大数量 83 | private int ttl = DEFAULT_TTL; //过期时间(秒) 84 | 85 | public int getMaxSize() { 86 | return maxSize; 87 | } 88 | 89 | public int getTtl() { 90 | return ttl; 91 | } 92 | } 93 | 94 | /** 95 | * 创建基于Caffeine的Cache Manager 96 | */ 97 | @Bean 98 | @Primary 99 | public CacheManager caffeineCacheManager() { 100 | SimpleCacheManager cacheManager = new SimpleCacheManager(); 101 | 102 | List caches = new ArrayList<>(); 103 | for (Caches c : Caches.values()) { 104 | caches.add(new CaffeineCache(c.name(), 105 | Caffeine.newBuilder().recordStats() 106 | .expireAfterWrite(c.getTtl(), TimeUnit.SECONDS) 107 | .maximumSize(c.getMaxSize()) 108 | .build()) 109 | ); 110 | } 111 | 112 | cacheManager.setCaches(caches); 113 | 114 | return cacheManager; 115 | } 116 | 117 | } 118 | ``` 119 | 120 | 测试: 121 | 122 | ``` 123 | @Slf4j 124 | @Service 125 | public class TestService { 126 | 127 | @Cacheable(cacheNames = "TEST_A") 128 | public int testA(){ 129 | log.info("testA not get from cache!"); 130 | return 100; 131 | } 132 | 133 | @Cacheable(cacheNames = "TEST_B") 134 | public int testB(){ 135 | log.info("testB not get from cache!"); 136 | return 99; 137 | } 138 | 139 | @Cacheable(cacheNames = "TEST_A") 140 | public String testC(){ 141 | log.info("testC not get from cache!"); 142 | return "testC"; 143 | } 144 | 145 | } 146 | ``` 147 | 148 | ## 项目地址 149 | 150 | [cases-caffeine](https://github.com/MyHerux/cases/tree/master/cases-caffeine) -------------------------------------------------------------------------------- /研发相关/系统设计/缓存/缓存之Caffeine.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MyHerux/code-note/854b0f86d319886564c00c21040a1745f88b10a2/研发相关/系统设计/缓存/缓存之Caffeine.md -------------------------------------------------------------------------------- /研发相关/系统设计/缓存/缓存之GuavaCache.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MyHerux/code-note/854b0f86d319886564c00c21040a1745f88b10a2/研发相关/系统设计/缓存/缓存之GuavaCache.md -------------------------------------------------------------------------------- /研发相关/系统设计/缓存/缓存之LFU算法.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MyHerux/code-note/854b0f86d319886564c00c21040a1745f88b10a2/研发相关/系统设计/缓存/缓存之LFU算法.md -------------------------------------------------------------------------------- /研发相关/系统设计/缓存/缓存之算法探究与实现.md: -------------------------------------------------------------------------------- 1 | # 缓存之算法探究与实现 2 | 3 | ## 0. Overview 4 | 5 | ## 1. 全部缓存 6 | 7 | - Overview 8 | 9 | 将从数据库里面查出来的数据保存到内存中,如果请求下次来就直接从内存中返回。 10 | 11 | - 使用 `HashMap` 来替代数据库查询 12 | 13 | ``` 14 | @Service 15 | public class HashMapCacheService { 16 | 17 | private HashMap hashMap = new HashMap<>(); 18 | 19 | @Resource 20 | private TestDbMapper dbMapper; 21 | 22 | public String getValue(String key) { 23 | String value = hashMap.get(key); 24 | if (value == null) { 25 | value = dbMapper.get(key); 26 | hashMap.put(key, value); 27 | } 28 | return value; 29 | } 30 | } 31 | ``` 32 | 33 | - 优点 34 | 35 | 实现简单方便,能够有效的减少对于DB的请求 36 | 37 | - 缺点 38 | 39 | `HashMap` 无法进行数据淘汰,内存会无限制的增长,如果是数据 `key` 比较恒定,但是访问频繁的类型,可以避免此缺点。 40 | 41 | 42 | ## 2. 数据淘汰 43 | 44 | - Overview 45 | 46 | 担心内存会无限制的增长,那我们直接采用算法淘汰一些数据不就行了么? 47 | 48 | ### 2.1. `FIFO` 算法 49 | 50 | - Overview 51 | 52 | 先进先出算法,先进入缓存的会先被淘汰。 53 | 54 | - 实现 55 | 56 | - 优点 57 | 58 | 实现简单 59 | 60 | - 缺点 61 | 62 | 命中率会很低,没法区分高频访问的缓存和低频访问的缓存。 63 | 64 | 65 | ### 2.2. `LRU` 算法 66 | 67 | - Overview 68 | 69 | 最近最少使用算法,每次访问数据都会将其重新放在我们的队尾,如果需要淘汰数据,就只需要淘汰队首即可 70 | 71 | - 实现 72 | 73 | 在 `LinkedHashMapMap` 中,所有 `put` 进来的 `Entry` 都保存在哈希表(`entry` 链表)中,但由于它又额外定义了一个以 `head` 为头结点的双向链表,因此对于每次 `get` 或者 `put`的时候,除了将其保存到哈希表中对应的位置上之外,还会将其插入到双向链表的尾部。 74 | 75 | 76 | 必须实现 `removeEldestEntry` ,否则不会发生淘汰,具体参考 `LinkedHashMap` 的实现。 77 | 78 | 在构造方法中,设置的大小特意设置到 `max*1.1` ,避免 `LinkedHashMap` 的自动扩容逻辑。 79 | 80 | ``` 81 | public class LRUHashMapCacheService { 82 | 83 | 84 | class LRULinkedHashMap extends LinkedHashMap { 85 | 86 | private final int max; 87 | private Object lock; 88 | 89 | public LRULinkedHashMap(int max, Object lock) { 90 | super((int) (max * 1.1f), 0.75f, true); 91 | this.max = max; 92 | this.lock = lock; 93 | } 94 | 95 | 96 | @Override 97 | protected boolean removeEldestEntry(Map.Entry eldest) { 98 | return size() > max; 99 | } 100 | 101 | public Object getValue(Object key) { 102 | synchronized (lock) { 103 | return get(key); 104 | } 105 | } 106 | public void putValue(Object key, Object value) { 107 | synchronized (lock) { 108 | put(key, value); 109 | } 110 | } 111 | 112 | 113 | public boolean removeValue(Object key) { 114 | synchronized (lock) { 115 | return remove(key) != null; 116 | } 117 | } 118 | public boolean removeAll(){ 119 | clear(); 120 | return true; 121 | } 122 | } 123 | 124 | 125 | } 126 | ``` 127 | 128 | - 优点 129 | 130 | 避免了 `FIFO` 的问题,高频词汇会延后淘汰。 131 | 132 | - 缺点 133 | 134 | 1. 无法避免时间段问题:比如在10分钟的前9分钟,高频数据被访问了1w次,但是接下来1分钟,高频词汇没有被访问,则高频词汇缓存会慢慢被移动到队首然后被淘汰。 135 | 136 | 2. 锁竞争严重 137 | 138 | 3. 不支持过期时间 139 | 140 | 4. 不支持自动刷新 141 | 142 | 5. 命中时需要遍历链表,找到命中的数据块索引,然后需要将数据移到头部。 143 | 144 | ### 2.3. `LFU` 算法 145 | 146 | - Overview 147 | 148 | 最近最少频率使用算法,对 `LRU` 算法进行了优化,利用额外的空间记录每个数据的使用频率,然后选出频率最低进行淘汰。 149 | 150 | - 实现 151 | 152 | [缓存之LFU算法](./缓存之LFU算法.md) 153 | 154 | - 优点 155 | 156 | 避免了 `LRU` 的问题,可以处理时间段的问题。 157 | 158 | - 缺点 159 | 160 | 实现成本较高,需要额外的空间消耗。 161 | 162 | 163 | ## 3. Guava cache 164 | 165 | - Overview 166 | 167 | [缓存之GuavaCache](./缓存之GuavaCache.md) 168 | 169 | 170 | ## 4. Caffeine 171 | 172 | - Overview 173 | 174 | [缓存之Caffeine](./缓存之Caffeine.md) -------------------------------------------------------------------------------- /研发相关/系统设计/缓存/缓存穿透、缓存击穿、缓存雪崩.md: -------------------------------------------------------------------------------- 1 | # 缓存穿透、缓存击穿、缓存雪崩 2 | 3 | 4 | ## 缓存穿透 5 | 6 | - 原因 7 | 8 | 缓存穿透指查询一个一定不存在的数据。由于缓存是命中时被动写的,并且出于容错考虑,如果从存储层查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到存储层去查询,失去了缓存的意义。 9 | 10 | 在流量大时,可能DB就挂掉了,如果有人利用不存在的key频繁攻击我们的应用,这就是漏洞。 11 | 12 | - 解决方案 13 | 14 | - 缓存不存在的数据 15 | 16 | 如果一个查询返回的数据为空(不管是数据不存在,还是系统故障),我们仍然把这个空结果进行缓存,但它的过期时间会设置得很短。 17 | 18 | - 过滤会造成穿透的查询 19 | 20 | 将所有可能存在的数据哈希到一个足够大的 `bitmap` 中,一个一定不存在的数据会被 这个 `bitmap` 拦截掉,从而避免了对底层存储系统的查询压力。 21 | 22 | ## 缓存雪崩 23 | 24 | - 原因 25 | 26 | 缓存雪崩是指在我们设置缓存时采用了相同的过期时间,导致缓存在某一时刻同时失效,请求全部转发到DB,DB瞬时压力过重雪崩。 27 | 28 | - 解决方案 29 | 30 | - 随机缓存过期时间 31 | 32 | 避免同时过期,可以将缓存的过期时间增加一个随机值,比如1-5分钟。这样每一个缓存的过期时间的重复率就会降低,就很难引发集体失效的事件。 33 | 34 | - 不在同一时间缓存 35 | 36 | 用加锁或者队列的方式保证缓存的单线 程(进程)写,从而避免失效时大量的并发请求落到底层存储系统上。 37 | 38 | ## 缓存击穿 39 | 40 | - 原因 41 | 42 | 缓存在某个时间点过期的时候,恰好在这个时间点对这个Key有大量的并发请求过来,这些请求发现缓存过期一般都会从后端DB加载数据并回设到缓存,这个时候大并发的请求可能会瞬间把后端DB压垮。 43 | 44 | - 解决方案 45 | 46 | - 使用互斥锁(mutex key) 47 | 48 | 就是在缓存失效的时候(判断拿出来的值为空),不是立即去 `load db` ,而是先使用缓存工具的某些带成功操作返回值的操作(比如 `Redis` 的 `SETNX` 或者 `Memcache` 的 `ADD`)去 `set` 一个 `mutex key` ,当操作返回成功时,再进行 `load db` 的操作并回设缓存;否则,就重试整个 `get` 缓存的方法。 49 | 50 | - 提前更新 51 | 52 | 在 `value` 内部设置 `1` 个超时值(timeout1),比实际的超时值小(timeout2)。当从 `cache` 读取到 `timeout1` 发现它已经过期时候,马上延长 `timeout1` 并重新设置到 `cache` 。然后再从数据库加载数据并设置到 `cache` 中。相当于在过期之前提前更新缓存数据。 53 | 54 | - "永远不过期" 55 | 56 | - `redis` 不设置过期时间 57 | 58 | - 为了避免数据成静态的,把过期时间存在 `key` 对应的 `value` 里,如果发现要过期了,通过一个后台的异步线程进行缓存的构建 59 | 60 | - 使用Hystrix做资源的隔离保护主线程池 61 | 62 | 63 | --- 64 | 65 | 面试的时候被问了一个问题:如何保证 `DB` 的数据实时更新到缓存? 66 | 67 | 想法: 68 | 69 | - 首先想到的是业务是否需要实时更新,因为本身缓存设置超时时间的话就是以时间间隔来更新的。所以业务不需要强一致实时性的数据的话可以不考虑这个问题。 70 | 71 | - 如果要更新,肯定不可能缓存去轮训,希望能由 `DB` 这边通知更新 72 | 73 | 74 | 资料查询: 75 | 76 | - 数据库和 `redis` 分别处理不同的数据类型 77 | 78 | - 使用脚本通知 `redis` 更新 79 | 80 | `MySQL binlog` 增量订阅消费+消息队列+处理并把数据更新到 `redis` -------------------------------------------------------------------------------- /研发相关/系统设计/负载均衡/README.md: -------------------------------------------------------------------------------- 1 | ## 负载均衡 2 | 3 | - [现代网络负载均衡与代理](./现代网络负载均衡与代理.md) -------------------------------------------------------------------------------- /研发相关/面试相关/12_28_问题.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | spring请求的生命周期,资源查找 4 | 5 | 6 | 线上问题排查 oom 7 | 8 | 9 | 自己实现消息事务 10 | 11 | mysql分库分表 12 | 13 | 自己实现的链路 14 | 15 | 16 | 17 | 18 | --------------------------------------------------------------------------------