开发者博客 – IT技术 尽在开发者博客

开发者博客 – 科技是第一生产力


  • 首页

  • 归档

  • 搜索

每天一个 Linux 命令(7)—— ping 命令简介 命

发表于 2021-11-12

这是我参与11月更文挑战的第12天,活动详情查看:2021最后一次更文挑战

命令简介

ping 命令是 Linux 系统中一个非常常用的网络命令。ping 命令主要用于测试网络的连通性,也可用于测试网络的性能和主机的响应能力。

日常工作中,我们经常会遇到网页无法打开、网址无法请求的情况。这个时候我们的一般操作是 ping 一下网址,比如 ping baidu.com

使用 ping 测试网络的连通性时,首先应测试本地系统,验证本地系统的网络接口是否已经启用,然后再测试本地网络中的主机,最后再测试其他目标主机的连通性。在测试过程中,ping 命令会计算最小、最大以及平均的往返传输时间,统计丢失的分组数据。

linux 下的 ping 命令和 windows 下的 ping 执行稍有区别,linux 下 ping 不会自动终止,需要按 ctrl+c 终止或者用参数 -c 指定要求完成的回应次数。

ping 命令采用基于 ICMP 协议的 ECHO_REQUEST 数据报探测指定的主机和网关,期望获得一个ICMP ECHO_RESPONSE 响应数据报。ECHO_REQUEST 数据报具有一个 IP 和 ICMP 头信息,紧随其后的是一个 timeval 结构数据,最后是任意数量的填充字节,以装配成完整的分组数据。其中,选用的参数 hops 是中间需要跨越的网关,dest 是目标系统的主机名或 IP 地址。

大多数情况下,我们可以根据 ping 命令输出的信息来确定目标主机是否可以访问(但这并不是绝对的)。有些服务器为了防止通过 ping 探测到,通过防火墙设置了禁止 ping 或者在内核参数中禁止 ping,这样就不能通过 ping 确定该主机是否还处于开启状态。

命令格式

1
css复制代码ping [参数] [主机名或IP地址]

命令参数

参数 解释
-a 测试网络连通性时增加声音提示。
-A 自适应连通性测试。自动调节分组数据之间的时间间隔以适应网络通信的往返响应时间,从而避免网络中出现一个或多个(如果指定了 -l 选项)以上的未响应的探测分组数据。对于普通用户而言,最小的时间间隔是 200 毫秒。在往返传输时间较低的网络中,这种测试方式相当于洪水式测试方式。
-b 允许测试广播地址。禁止 ping 修改探测分组数据的源地址(绑定到 ping开始运行时选用的地址)。
-c count 在发送指定数量 count 的 ECHO_REQUEST 分组数据之后停止。与 -w 选项一起使用时,ping 命令将会等待指定数量 count 的 ECHO_REPLY 分组数据,直至超时。
-d 设置当前所用套接字的 SO_DEBUG 选项。实际上,Linux 内核并未使用这个套接字选项。
-f 极限检测。大量且快速地送网络封包给一台机器,看它的回应。
-i intvl 秒数:设定间隔几秒送一个网络封包给一台机器,预设值是一秒送一次。
-I 网络界面:使用指定的网络界面送出数据包。
-l 前置载入:设置在送出要求信息之前,先行发出的数据包。
-n 只输出数值。显示 IP 地址即可,无须把 IP 地址解释成符号名字。
-p 范本样式:设置填满数据包的范本样式。
-q 安静输出模式。 不显示任何传送封包的信息,只显示最后的结果。
-r 绕过常规的路由表,直接向网络接口连接的主机发送分组数据。
-R 记录路由过程。
-s 指定发送的数据字节的数量,默认的字节数是 56,与 8 字节的 ICMP 头信息组合后,构成 64 字节的 ICMP 分组数据。
-t 存活数值:设置存活数值 TTL 的大小。
-v 详细显示指令的执行过程。
-w 终止值:指定 ping 命令终止执行的超时值(单位为秒)。

应用实例

  1. 测试本地系统与远程主机之间的网络连通性,以及远程主机是否正在运行。
1
2
3
4
python复制代码ping baidu.com
PING baidu.com (220.181.38.251): 56 data bytes
64 bytes from 220.181.38.251: icmp_seq=0 ttl=49 time=10.255 ms
64 bytes from 220.181.38.251: icmp_seq=1 ttl=49 time=9.296 ms
  1. ping 失败
1
2
3
4
5
bash复制代码ping -nA www.google.com.hk
PING www-wide.l.google.com (74.125.23.199): 56 data bytes
Request timeout for icmp_seq 0
Request timeout for icmp_seq 1
Request timeout for icmp_seq 2

参考文档

  • ping命令

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

SpringBoot整合MybatisPlus 实现多租户

发表于 2021-11-12

代码已经上传到码云:gitee.com/lezaiclub/s…欢迎白嫖

引言

今天我们来聊聊多组户 其实多租户主要讲的是数据隔离,即每个企业或用户都享有自己的独立数据,不和其他人的数据相互掺合,别人也是无法获取我们自己的数据的。 多租户在实现上主要有三种方式:

独立数据库

这种方式最简单明了,每个企业或用户在平台上通过独立的数据库来隔离自己的数据,这是在物理上达到了数据的隔离,这也是它的优点所在,但是他的缺点是,为每个企业或用户创建独立的数据库,成本非常大,而且空间的利用率也不高,造成严重的浪费。总结下:

  • 优点:数据完全隔离、安全性高
  • 缺点:成本高,数据库多,难以维护

同一数据库,不同表

这种方式是在逻辑上进行隔离,不同用户的数据都在同一个数据库中,但是使用不同的表来存储不同用户的数据,实现数据的隔离,这种方式相对上面,成本下降了,也同样达到了数据隔离

同一数据库,同一张表,通过字段区分

这种方式相对上面两种,成本就更加少了,仅仅通过字段就可以区分不同的数据,这种方式维护简单,成本少,但是进行数据导出和迁移,却是一种大大的麻烦,总结下

  • 优点:维护方便、成本低、实现简单,维护的租户数量可以有很多
  • 缺点:数据好迁移,数据没有完全做到隔离

通过对比上面三种方式,我们已经清楚了每种实现方案的区别及其他们的优劣势,在本文,我们将通过集成mybatisPlus,实现第三种方式,来实现多租户。 ​

环境搭建

基于上一节的环境,我们已经搭集成了mybatisPlus的环境。 现在我们在member表中新增一个字段tenant_id,用来保存租户信息,同样如果你的表中需要维护租户信息,也需要创建同样的一个字段

1
2
3
4
sql复制代码ALTER TABLE  `member` 
ADD COLUMN `tenant_id` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL COMMENT '租户id' AFTER `member_level`,
DROP PRIMARY KEY,
ADD PRIMARY KEY (`id`) USING BTREE;

coding

添加请求上下文辅助类

这个类主要是保存当前请求用户的的信息,使用threadlocal来实现,和当前请求线程绑定

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
typescript复制代码package com.aims.mybatisplus.conf;

public class TenantRequestContext {
    private static ThreadLocal<String> tenantLocal = new ThreadLocal<>();

    public static void setTenantLocal(String tenantId) {
        tenantLocal.set(tenantId);
    }

    public static String getTenantLocal() {
        return tenantLocal.get();
    }

    public static void remove() {
        tenantLocal.remove();
    }
}

添加认证拦截器

这个拦截器主要是获取请求头中的租户id,然后放到上下文中,供mybatisPlus获取

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
java复制代码package com.aims.mybatisplus.interceptor;

import com.aims.mybatisplus.conf.TenantRequestContext;
import com.mysql.cj.util.StringUtils;
import org.springframework.web.servlet.HandlerInterceptor;

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

public class AuthInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String userId = request.getHeader("tenant_id");
        if (!StringUtils.isNullOrEmpty(userId)) {
            TenantRequestContext.setTenantLocal(userId);
            System.out.printf("当前租户ID:"+userId);
        }
        return HandlerInterceptor.super.preHandle(request, response, handler);
    }
}

配置拦截器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
java复制代码package com.aims.mybatisplus.interceptor;


import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class LoginConfig implements WebMvcConfigurer {
    
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        //注册拦截器
        InterceptorRegistration registration = registry.addInterceptor(new AuthInterceptor());
        registration.addPathPatterns("/**");                      //所有路径都被拦截
        registration.excludePathPatterns(                         //添加不拦截路径
                                         "你的登陆路径",            //登录
                                         "/**/*.html",            //html静态资源
                                         "/**/*.js",              //js静态资源
                                         "/**/*.css",             //css静态资源
                                         "/**/*.woff",
                                         "/**/*.ttf"
                                         );    
    }
}

添加mybatisPlus配置类

该类主要是配置mybatisPlus拦截器,用来配租户ID字段,哪些表可以获取租户处理,租户ID从上下文中获取

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
less复制代码package com.aims.mybatisplus.conf;

import com.baomidou.mybatisplus.autoconfigure.ConfigurationCustomizer;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.handler.TenantLineHandler;
import com.baomidou.mybatisplus.extension.plugins.inner.TenantLineInnerInterceptor;
import net.sf.jsqlparser.expression.LongValue;
import net.sf.jsqlparser.expression.StringValue;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.schema.Column;

@Configuration
public class MybatisConfig {
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        interceptor.addInnerInterceptor(new TenantLineInnerInterceptor(new TenantLineHandler() {
            @Override
            public Expression getTenantId() {
            // 从上下文中获取
                return new StringValue(TenantRequestContext.getTenantLocal());
            }

            // 这是 default 方法,默认返回 false 表示所有表都需要拼多租户条件
            @Override
            public boolean ignoreTable(String tableName) {
                return !"member".equalsIgnoreCase(tableName);
            }
      // 数据表中对应的租户字段,这里是默认字段
            @Override
            public String getTenantIdColumn() {
                return "tenant_id";
            }
        }));
        return interceptor;
    }
}

当前目录结构 在这里插入图片描述

编写测试接口

​

注意租户信息需要写在请求头里面的, 在这里插入图片描述

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
typescript复制代码@RestController
public class TenantController {
    @Autowired
    private MemberMapper memberMapper;
  
    // 测试插入操作
    @RequestMapping("/testTenant")
    public String testTenantId() {
        Member member = new Member();
        member.setMemberName("测试租户ID");
        memberMapper.insert(member);
        return "success";
    }
  
    //测试查询操作
    @RequestMapping("/getCurrentTenantMember")
    public List<Member> getCurrentTenantMember() {
        QueryWrapper<Member> queryWrapper = new QueryWrapper<>();
        return memberMapper.selectList(queryWrapper);
    }
}

最后

通过上面演示,相信大家应该都已经实现了多组户,后面会有更多mybatisPlus实战教程分享给大家

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

【算法攻坚】双指针技巧解题

发表于 2021-11-12

这是我参与11月更文挑战的第12天,活动详情查看:2021最后一次更文挑战

前言

今天的题目按照顺序的话应该是第8题字符串转换整数 (atoi),但是这道题完全是对字符串的边界转换考察,个人认为并没有什么算法思想,所以这次跳过第8题。

今日题目1

给你一个整数 x ,如果 x 是一个回文整数,返回 true ;否则,返回 false 。

回文数是指正序(从左向右)和倒序(从右向左)读都是一样的整数。例如,121 是回文,而 123 不是。

示例 1:

输入:x = 121
输出:true

示例 2:

输入:x = -121
输出:false
解释:从左向右读, 为 -121 。 从右向左读, 为 121- 。因此它不是一个回文数。

示例 3:

输入:x = 10
输出:false
解释:从右向左读, 为 01 。因此它不是一个回文数。

示例 4:

输入:x = -101
输出:false

进阶:你能不将整数转为字符串来解决这个问题吗?

思路

前面刷到的第5题,最长回文子串中已经介绍了回文的意思

这里回文数,其实也是类似,回文数是指正序(从左向右)和倒序(从右向左)读都是一样的整数

这道题思路也是通过双指针方式,来判断前后是否相同

如果数字为负数,直接返回false,因为有‘-’所以一定不是回文数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
java复制代码public boolean isPalindrome(int x) {
if( x < 0){
return false;
}
String s = String.valueOf(x);
int left = 0, right = s.length()-1;

while(left <= right){
if(s.charAt(left) == s.charAt(right)){
left++;
right--;
}else{
return false;
}
}
return true;
}

执行用时:6 ms, 在所有 Java 提交中击败了96.93%的用户

内存消耗:37.9 MB, 在所有 Java 提交中击败了46.32%的用户

解法二

再次看下回文数的定义 回文数是指正序(从左向右)和倒序(从右向左)读都是一样的整数

也就说把数字翻转后与翻转前数值相当,这时就可以利用前文的整数翻转思路

1
2
3
4
5
6
7
8
9
10
11
12
13
java复制代码public boolean isPalindrome(int x) {
if( x < 0){
return false;
}
int temp = x;
long result = 0;
while(x != 0){
int cuur = x %10;
result = result * 10 + cuur;
x = x /10;
}
return temp == result;
}

执行用时:5 ms, 在所有 Java 提交中击败了98.60%的用户

内存消耗:37.8 MB, 在所有 Java 提交中击败了54.14%的用户

今日题目2

给你 n 个非负整数 a1,a2,…,an,每个数代表坐标中的一个点 (i, ai) 。在坐标内画 n 条垂直线,垂直线 i 的两个端点分别为 (i, ai) 和 (i, 0) 。找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。

说明:你不能倾斜容器。

示例 1:

img

输入:[1,8,6,2,5,4,8,3,7]
输出:49
解释:图中垂直线代表输入数组 [1,8,6,2,5,4,8,3,7]。在此情况下,容器能够容纳水(表示为蓝色部分)的最大值为 49。

示例 2:

输入:height = [1,1]
输出:1

示例 3:

输入:height = [4,3,2,1,4]
输出:16

示例 4:

输入:height = [1,2,1]
输出:2

提示:

n == height.length
2 <= n <= 105
0 <= height[i] <= 104

思路

本题解法也是双指针,一开始两个指针一个指向开头一个指向结尾,此时容器的底是最大的,

接下来随着指针向内移动,会造成容器的底变小,在这种情况下想要让容器盛水变多,就只有在容器的高上下功夫。

那该如何决策哪个指针移动呢?我们能够发现不管是左指针向右移动一位,还是右指针向左移动一位,容器的底都是一样的,都比原来减少了 1。

这种情况下我们想要让指针移动后的容器面积增大,就要使移动后的容器的高尽量大,所以我们选择指针所指的高较小的那个指针进行移动,这样我们就保留了容器较高的那条边,放弃了较小的那条边,以获得有更高的边的机会。

把每次移动后的体积最大值保存,然后移动指针,为什么移动小的指针就可以呢?

这是因为高度由两边中矮的一边决定,

所以如果移动高的一边,即使遇到再高的,体积也不会变大

如果移动短的就可能变大,也可能变小

实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
java复制代码public int maxArea(int[] height) {
int result = 0;
int left = 0, right = height.length -1;
while(left < right){
//两边哪个短,移动哪个指针
if(height[left] < height[right]){
result = Math.max(result, height[left]*(right - left));
left++;
}else{
result = Math.max(result, height[right]*(right - left));
right--;
}
}
return result;
}

执行用时:3 ms, 在所有 Java 提交中击败了92.67%的用户

内存消耗:51.8 MB, 在所有 Java 提交中击败了64.99%的用户

小结

从力扣第9/10题可以看出,双指针在解题中很常见,也很有用,通过几道题明显的感觉到比暴力破解快很多,类似的还有前文描述的三数之和等都是使用了双指针。

双指针,或者跟多的三指针问题都是有着一定的规律,这些题都有着不断压缩搜索距离的特征,以后再遇到类似的题目可以轻车熟路。

今天多学一点,明天就少说一句求人的话,加油!

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

Windows配置kubectl windows下配置kub

发表于 2021-11-12

「这是我参与11月更文挑战的第5天,活动详情查看:2021最后一次更文挑战」。

目前,Kubernetes技术已经成为各大互联网公司争相使用的热门技术,它不仅可以提高研发部署效率,而且可以减少物理部署的机器规模,为企业节省成本。

Kubernetes通过kube-apiserver作为整个集群管理的入口。Apiserver是整个集群的主管理节点,用户通过Apiserver配置和组织集群,同时集群中各个节点同etcd存储的交互也是通过Apiserver进行。

Apiserver实现了一套RESTfull的接口,用户可以直接使用API同Apiserver交互。另外官方还提供了一个客户端kubectl随工具集打包,用于可直接通过kubectl以命令行的方式同集群交互。

windows下配置kubectl

下载

这里使用的是v1.21版本的kubectl,对应Windows版本kubectl下载链接如下:dl.k8s.io/release/v1.…
下载后,这里讲可执行文件kubectl.exe放置在D:\tools\k8s目录中。

配置环境变量

右键点击【此电脑】,选择【属性】,找到【高级系统配置】,然后在系统环境变量的Path中增加 D:\tools\k8s,此目录即是存放kubectl.exe的目录。将kubectl可执行文件目录配置好后,在terminal命令行中执行kubectl version可以看到kubectl版本信息,即表明安装成功。

配置kubeconfig

kubectl默认会从$HOME/.kube目录下查找文件名为 config 的文件,也能通过设置环境变量 KUBECONFIG 或者通过设置去指定其它 kubeconfig 文件,kubeconfig就是为访问集群所作的配置。在Windows中对应的目录在C:\Users\xxx.kube下的config文件,我们可以把线上kubernetes集群的配置信息放入config文件中去,这样我们就可以用kubectl直接访问kubernetes集群。

进入在当前windows用户的home目录,选择对应用户名,例如我用的账号是Administrator,进入目录C:\Users\Administrator,在里面创建文件夹.kube。

登录线上k8s

登录到可以执行kubectl的Linux服务器,去目录~/.kube/,将里面的config文件下载下来,放到上一步创建的.kube目录下。

image.png

在命令行通过kubectl操作对应kubernetes

回到windows电脑,打开控制台,进入kubectl.exe文件所在目录,即可通过kubectl对kubernetes环境进行操作,如下图:

至此,windows环境下已经可以远程操作kubernetes环境了。

常用操作命令

帮助命令

1
2
3
4
5
bash复制代码# 帮助命令:查看kubectl支持的命令参数
kubectl --help

## 查看kubectl安装的版本
kubectl version

查询命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
ini复制代码# 查询节点信息
kubectl get nodes

# 查询节点更多信息
kubectl get nodes -o wide

# 暂停节点
kubectl cordon node_xxx

# 恢复节点
kubectl uncordon node_xxx

# 温和的驱除节点(操作需谨慎)
kubectl drain node_xxx

# 暴力删除(操作需谨慎)
kubectl delete node node_xxx


# 查看副本数,默认都是一个
kubectl get deployments

# 设置副本数量,增加到3个
kubectl scale deployments/xxx --replicas=3

# 重新缩减到一个
kubectl scale deployments/xxx --replicas=1

参考

在windows电脑上配置kubectl远程操作kubernetes
Windows下安装kubectl及Node和Pod操作常用命令
Kubernetes之kubectl常用命令

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

【mongo 系列】mongodb 学习十二,MongoDB

发表于 2021-11-12

「这是我参与11月更文挑战的第 11 天,活动详情查看:2021最后一次更文挑战」

简单回顾

上次我们说到了关于 mongodb 的集群,分为主从集群和分片集群,对于分片集群中的分片这里需要注意如下几点,一起来回顾一下:

  • 对于热点数据

某些分片键(分片键是集合中每个文档中存在的索引字段或复合索引字段)会导致所有的 读或者写请求 都操作在单个数据块或者分片上,这样就会导致单个分片服务器负荷过重,那么自增长的分片键容易导致写的问题

  • 对于不可分割的数据块

对于粗粒度的分片键,可能会导致许多文档使用相同的分片键

这样的话这些文档就不能被分割为多个数据块,这就会限制了mongodb 的均匀分布数据能力

  • 对于查询障碍

分片键与查询是没有关联的,这样会造成糟糕的查询性能

对于以上注意点,咱们做到心中有数,实际工作中遇到类似的问题,就可以尝试学着处理了

今天我们简单了解一下 mongodb 的存储引擎是个啥

存储引擎

说到 mongodb 的存储引擎,我们要知道是在 mongodb 3.0 的时候引入了可插拔存储引擎的概念

现在主要有这几个引擎:

  • WiredTiger 存储引擎
  • inMemory 存储引擎

在存储引擎刚出来的时候,默认是使用的 MMAPV1 存储引擎的

MMAPV1 引擎,看名字我们大概就知道他是使用的是 mmap 来做的,运用的是 linux 内存映射的原理

现在不使用 MMAPV1 引擎,是因为 WiredTiger 存储引擎更优,例如对比一下 WiredTiger 就有如下优势:

  • WiredTiger 读写操作性能更好

WiredTiger 能更好的发挥多核系统的处理能力

  • WiredTiger 锁的粒度更小

MMAPV1引擎使用表级锁,当某个单表上有并发的操作,吞吐就会受到限制

而 WiredTiger 使用文档级的锁 ,这就带来并发及吞吐的提高

  • WiredTiger 压缩方式更好

WiredTiger 使用前缀压缩,比起 MMAPV1 更节省对内存空间的损耗

并且 WiredTiger 还提供压缩算法, 这样就可以大大降低对硬盘资源的消耗

WiredTiger 引擎 的写入原理

通过上图我们可以看出, WiredTiger 写入磁盘的原理也是很简单的

  • 应用请求来到 mongodb ,mongodb 做处理,并将结果存入缓存中
  • 当缓存中达到 2 个 G 的时候,或者 当 60 s 定时器到时间的时候,就会将缓存中的数据刷到磁盘中去
    细心的 xdm 就知道,那么如果现在正好是 59 秒,1个多 G 的时候,缓存中的数据还没有同步到磁盘中,mongodb 就异常挂掉了,那么 mongodb 岂不是会丢失数据?

我们用手指头都可以想到,mongodb 的设计者怎么会让这种情况存在,那么必然会有解决方案,如下

如上图,图中多了一个 journaling buffer 和 journal 文件

  • journaling buffer

存放 mongodb 增删改 指令的缓冲区

  • journal 文件

类似于关系数据库中的事务日志

引入 Journaling 的目的是:

Journaling 能够使 mongodb 数据库由于意外故障后快速恢复

Journaling 日志功能

Journaling 的日志功能,看上去有点像是 redis 中的 aof 持久化一样,也只能说是类似

在 mongodb 2.4 的时候,就已经是 默认会开启 Journaling日志功能 的,我们启动 mongod 实例的时候,服务就会去检查是否需要恢复数据

因此就不会有上述 mongodb 丢数据的情况了

另外这里我们要知道,journaling 的日志功能,当 mongodb 需要进行写操作的时候,也就是 增,删,改的时候,journaling 是会写日志的,这会影响性能

但是 mongodb 读取操作的时候,是不会记录到缓存中的,因此也不会记录到 journaling 日志中,因此读操作没有影响

今天就到这里,学习所得,若有偏差,还请斧正

欢迎点赞,关注,收藏

朋友们,你的支持和鼓励,是我坚持分享,提高质量的动力

好了,本次就到这里

技术是开放的,我们的心态,更应是开放的。拥抱变化,向阳而生,努力向前行。

我是小魔童哪吒,欢迎点赞关注收藏,下次见~

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

java中程序进程、线程、多线程,线程同步、synchron

发表于 2021-11-12

一、进程&线程

  1. 程序
    是为完成特定的任务或需求,而用某种语言编写的一组指令的集合
  2. 进程

是指一个内存中运行的应用程序,每个进程都有一个独立的内存空间
3. 线程

进程的一个执行路径,共享一块内存,进程至少有一个线程

并发执行,是进程的进一步划分
4. 线程调度

分时调度

轮流使用cpu,平均分配cpu使用率

抢占式调度

让优先级更高的先使用cpu,优先级相同的随机分配

java默认抢占式调度

java的调度方法

同优先级线程组成先进先出队列(先到先服务),使用时间片策略

对高优先级,使用优先调度的抢占式策略
5. 线程优先级

**MAX_PRIORITY:10

NORM_PRIORITY:5

MIN_PRIORITY:1**

  • 方法

getPriority|返回此线程的优先级|

setPriority(int newPriority)|更改此线程的优先级,默认5,1~10

etDaemon|守护线程,全为守护线程JVM退出|

对于CPU的一个核来说,同一时刻只能运行一个线程,多个线程轮流切换非常快。我们看做是同一时刻多个任务执行
多线程程序并不能提高程序的运行速度,但能够提高程序运行效率,让CPU的 使用率更高。

  1. 线程分类

守护线程

用来服务用户线程的

通过在start方法前调用thread.setDaemon(true)可以把一个用户线程变成守护线程

若JVM都是守护线程,当前JVM将退出

JVM垃圾回收就是典型的守护线程

用户线程
7. 同步&异步

1
2
复制代码 同步 			排队执行,效率低 		
异步 同时执行,效率高但数据不安全
  1. 并发&并行
1
2
3
4
markdown复制代码 并发  
指两个或多个事件在一个时间段内的发生
并行
指两个或多个事件在同一时刻执行

二、线程的创建方式

1.Thread类

概述
  • java.lang.Thread
  • 每个线程都是通过Thread对象的run()方法完成操作,run()方法体称为:线程体。
  • 通过该Thread对象的start()方法来启动这个线程,而非直接调用run方法。
  • 一个线程对象只能调用一次start,如果重复调用,抛出lllegalThreadStateException异常
构造
  • Thread():创建新的对象
  • Thread(String threadName):创建线程并指定线程实例名
  • Thread(Runnable target):指定线程的目标对象,它实现了runnable接口的run方法
  • Thread(Runnable target,String name):创建新的Thread对象
方法
  1. start
    启动线程
  2. getName
    获得线程名
  3. setName
    设置线程名
  4. currentThread
    返回当前线程
  5. run
    真正的线程任务的方法
  6. setDaemon()
    参数为true,设置该线程为守护线程
    必须在线程启动之前,否则报IllegalThreadStateException
  7. yield
    线程让步
    暂停当前正在执行的线程,把机会让给优先级相同或者更高的线程
    若队列中没有同优先级的线程,忽略此方法
  8. join
    线程阻塞,加入线程运行
    当某个程序执行流中调用其他线程的join()方法时,调用线程将被阻塞,直到join()方法加入的join线程执行完为止
    低优先级的线程也可以获得执行
  9. sleep
    线程休眠
    令当前活动线程在指定时间段内放弃对CPU控制,使其他线程有机会被执行,时间到后重排队
    抛出InterruptedException异常
  10. isAlive
    判断线程是否存活
  11. interrupt
    中断线程
  12. interrupted
    测试当前线程是否已被中断
创建线程方式

创建 继承Thread类的类的对象,该类重写run方法

1
2
bash复制代码	A a = new A();
a.start();

2.Runnable接口

创建方式

类实现Runnable接口,重写run方法,创建该类得到对象任务,new Thread(类对象)来得到线程对象

好处
  • 通过创建任务,然后线程分配的方式来实现的多线程,更适合多个线程同时执行相同任务的情况
  • 可以避免单继承所带来的局限
  • 任务与线程本身是分离的,提高了程序的健壮性
  • 后续学习的线程池技术,接收Runnable类型的任务,不接受Thread类的线程

3.Callable接口

创建方式
  1. 编写类实现Callable接口 , 实现call方法
1
2
3
4
5
6
java复制代码class XXX implements Callable<T> {
@Override
public <T> call() throws Exception {
return T;
}
}
  1. 创建FutureTask对象 , 并传入第一步编写的Callable类对象
1
java复制代码FutureTask<Integer> future = new FutureTask<>(callable);
  1. 通过Thread,启动线程
1
java复制代码new Thread(future).start();
FutureTask对象
1
2
3
4
5
6
7
8
9
arduino复制代码get
如果需要等待计算完成,然后检索其结果
get​(long timeout, TimeUnit unit)
如果需要,最多等待计算完成的给定时间,然后检索其结果(如果可用)。
TimeUnit定义单位
isDone
判断子线程是否完成
cancel
传入true表示取消该方法,返回值也是boolean,返回false表示该任务已经完毕,无法取消了,返回true就是取消成功

4.Runnable与Callable

接口定义

1
2
3
4
5
6
7
8
9
csharp复制代码接口定义
//Callable接口
public interface Callable<V> {
V call() throws Exception;
}
//Runnable接口
public interface Runnable {
public abstract void run();
}

相同点

1
2
3
scss复制代码都是接口
都可以编写多线程程序
都采用Thread.start()启动线程

不同点

1
2
scss复制代码Runnable没有返回值;Callable可以返回执行结果
Callable接口的call()允许抛出异常;Runnable的run()不能抛出

获取返回值

1
2
csharp复制代码Callalble接口支持返回执行结果,需要调用FutureTask.get()得到,
此方法会阻塞主进程的继续往下执行,如果不调用不会阻塞。

5.线程池

  Executors一个容纳多个线程的容器,池中的线程可以反复使用,省去了频繁创建线程对象的操作

三、线程安全&同步

1.同步

同步锁机制
1
2
3
4
5
markdown复制代码	在《Thinking in java》中,是这么说的:对于并发工作,
你需要某种方式来防止两个任务访问相同的资源(其实就是共享资源竞争)。
防止这种冲突做法:当资源被一个任务使用时,在其上加锁。第一个访问
某项资源的任务必须锁定这项资源,使其他任务在其解锁之前,就无法访问它了
,而在其被解锁之时,另一个任务就可以锁定并使用它了。
注意
1
2
3
4
kotlin复制代码	必须确保使用同一资源的多个线程共用一把锁,非常重要。否则无法保证共享资源
的安全
一个线程类中所有静态方法共用同一把锁(类名.class),所有非静态方法共用
同一把锁(this),同步代码块(指定需谨慎)
同步的范围
1
2
3
复制代码1.明确哪些是多线程执行的代码
2.多个线程是否有共享的数据
3.明确多个线程运行代码中是否有多条语句操作共享数据

所有操作共享数据的这些语句都要放在同步范围中

释放锁操作
1
2
3
4
5
bash复制代码1.当前线程的同步方法、同步代码块执行结束
2.线程在执行中遇到break、return代码
3.出现未处理的Error、Exception,导致异常结束
4.执行了当前线程对象的wait方法,当前线程暂停,释放锁
join也会释放锁资源
不会释放锁操作
1
2
3
scss复制代码1.线程执行同步代码块或同步方法时,调用Thread.sleep()\Thread.yield()方法暂停当前线程执行
yeild()方法不释放锁资源的原因。 yeild()方法是暂停当前线程,让当前线程回到了可执行状态,但是不一定释放锁资源(可能释放锁资源,也可能不会释放锁资源)。被yeild()暂停的线程可能会回到可执行状态之后立即执行。因此可能不会释放锁资源。
2.线程执行同步代码块时,其他线程调用了该线程的suspend()方法将该线程挂起,该线程不会释放锁(同步监视器)

应尽量避免使用suspend和resume来控制线程

2.synchronized

  • 任何对象 都可作为锁对象,同一任务含有同一锁对象才会保证线程同步
    同步代码块
  • synchronized(obj){ }
  • 锁自己指定,可以使this或者className.class
    同步方法
  • public synchronized void test(){ }
  • 静态方法的锁是 className.class
  • 非静态方法锁是this

3.死锁

1
2
3
复制代码不同线程分别占用对方需要同步的资源,不放弃,都在等待对方让步,形成线程死锁
出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于阻塞状态,无法
继续

解决

  • 专门的算法、原则
  • 尽量减少同步资源的定义
  • 尽量避免嵌套同步

4.Lock锁

JDK5.0开始,加入通过显示定义同步锁对象来实现同步。同步锁使用Lock对象充当

  • java.util.concurrent.locks.Lock接口是控制多个线程对共享资源进行访问的工具。
  • 锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前应先获得Lock对象
  • ReentrantLock类实现了Lock,它拥有与synchronized相同的并发性和内存语义。比较常用ReentrantLock,可以显示加锁,释放锁
  • 格式
1
2
3
4
5
6
7
8
java复制代码 		//加锁
lock.lock();
try{
//代码
}finally {
//释放锁
lock.unlock();
}

5.synchronized与Lock锁比较

  • Lock是显示锁(手动开启和释放锁),synchronized是隐式锁,出了作用域自动释放
  • Lock只有代码块锁,synchronized有代码块和方法锁
  • 使用Lock锁,JVM将花费较少时间来调度来调度线程,性能更好。并具有良好扩展性(提供更多的子类)
  • 优先使用顺序
  • 同步代码块(已经进入方法体,分配了相应资源)->同步方法(在方法体外)*

6.线程通信

  • wait():令当前线程挂起并放弃CPU、同步资源并等待,使别的线程可访问并修改共享资源,而当前线程等待其他线程调用notify和notifyAll方法唤醒,唤醒后等待重新获得对监视器的所有权后才能继续执行
  • notify():唤醒正在排队等待同步资源的线程中优先级最高者结束等待。
  • noyifyAll():唤醒正在排队等候资源的所有线程结束等待
    注意
     这三个方法只有在synchronized方法或synchronized代码块中才能使用,否则报java.lang.lllegalMonitorstateException异常
      因为这三个方法必须有锁对象调用,而任意对象都可作为synchronized的同步锁,因此这三个方法只能声明在Object类中

四、线程池

1.概述

  • 背景
      如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低 系统的效率,因为频繁创建线程和销毁线程需要时间
  • Executors一个容纳多个线程的容器,池中的线程可以反复使用,省去了频繁创建线程对象的操作

2.好处

  • 降低资源消耗。
  • 提高响应速度。
  • 提高线程的可管理性

3.四种线程池

(1)缓存线程池:newCachedThreadPool
  • ExecutorService service = Executors.newCachedThreadPool();
  • (长度无限制)
  • 过程:
    1. 判断线程池是否存在空闲线程
    2. 存在则使用
    3. 不存在,则创建线程 并放入线程池, 然后使用
(2)定长线程池:newFixedThreadPoll
  • ExecutorService service = Executors.newFixedThreadPool()
  • (长度固定)
  • 过程:
    1. 判断线程池是否存在空闲线程
    2. 存在则使用
    3. 不存在空闲线程,且线程池未满的情况下,则创建线程 并放入线程池, 然后使用
  1. 不存在空闲线程,且线程池已满的情况下,则等待线程池存在空闲线程
(3)单线程线程池:newSingleThreadExecutor
  • ExecutorService service = Executors.newSingleThreadExecutor()
  • 1个
  • 过程:
    1. 判断线程池 的那个线程 是否空闲
    2. 空闲则使用
    3. 不空闲,则等待 池中的单个线程空闲后 使用
(4)周期性任务定长线程池:newScheduledThreadPool
  • ScheduledExecutorService service = Executors.newScheduledThreadPool(corePoolSize);

  • 周期任务 定长线程池

  • 过程

    1. 判断线程池是否存在空闲线程
    2. 存在则使用
    3. 不存在空闲线程,且线程池未满的情况下,则创建线程 并放入线程池, 然后使用
    4. 不存在空闲线程,且线程池已满的情况下,则等待线程池存在空闲线程
  • 周期性任务执行

    • 定时任务

      schedule(Callable callable,long delay, TimeUnit unit);

        参数1command. runnable类型的任务

        参数2delay. 时长数字

        参数3unit. 时长数字的单位

      • 周期性任务

        scheduleAtFixedRate(Runnable command,long initialDelay, long period, TimeUnit unit);

          参数1command. runnable类型的任务

          参数2initialDelay. 时长数字(延迟执行的时长)

          参数3period. 周期时长(每次执行的间隔时间)

          参数4unit. 时长数字的单位

  • 注意生成对象为ScheduledExecutorService

4.常用参数

  • corePoolSize:核心池的大小、
  • maximumPoolSize:最大线程池数
  • keepAliveTime:无任务时线程最多保持时间会终止

5.相关API

ExecutorService和Excutors

ExecutorService: 真正线程池接口。常见子类:ThreadPoolEcecutor
Excutors: 工具类、线程池的工厂类,用于创建并返回不同类型的线程池
有四种线程池:newFixedThreadPool、newCachedThreadPool、newScheduledThreadPool、newSingleThreadPool

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

Java也可以非常简单的实现图片灰度化

发表于 2021-11-12

「这是我参与11月更文挑战的第11天,活动详情查看:2021最后一次更文挑战」。

本文通过一个简单的实例,演示如何使用java来实现图片灰度化处理,主要借助下面两种策略来处理颜色

灰度化公式

1
ini复制代码avgColor = red * 0.299f + green * 0.587f + blue * 0.114f

均值方式

1
ini复制代码avgColor = (red + green + blue) / 3.0f

基于上面两种方式,我们要实现一个图片灰度化的处理,无非就是获取图片的每个像素点的颜色,然后计算avgColor,再用新的颜色填充即可

一个基础的实现演示如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
java复制代码private Color avg1(int red, int green, int blue) {
int avg = Math.round((red * 0.299f + green * 0.587f + blue * 0.114f));
return new Color(avg, avg, avg);
}
private Color avg2(int red, int green, int blue) {
int avg = Math.round((red + green + blue) / 3);
return new Color(avg, avg, avg);
}

@Test
public void testRender() throws IOException {
String file = "http://i0.download.fd.52shubiao.com/t_960x600/g1/M00/10/17/oYYBAFWvR5-IeXHuAAd5kPb8eSgAACm0QF50xIAB3mo414.jpg";
// 从网络上下载图片
BufferedImage img = ImageIO.read(FileReadUtil.getStreamByFileName(file));


int w = img.getWidth(), h = img.getHeight();
// 创建新的灰度图片画板
BufferedImage gray = new BufferedImage(w, h, img.getType());
Graphics2D g2d = gray.createGraphics();
g2d.setColor(null);
g2d.fillRect(0, 0, w, h);

for (int x = 0; x < w; x++) {
for (int y = 0; y<h; y++) {
// 针对像素点的颜色灰度化之后,重新绘制
int color = img.getRGB(x, y);
Color grayColor = avg1((color & 0xff0000) >> 16, (color & 0xff00) >> 8, color & 0x0000ff);
g2d.setColor(grayColor);
g2d.fillRect(x, y, 1, 1);
}
}
g2d.dispose();
System.out.printf("渲染完成");
}

生成原图与灰度图的对比如下

00.jpg

注意上面的实现,其中加载网络图片的具体实现,之前的博文有介绍,有兴趣的小伙伴可以参考: 封装一个根据路径获取文件资源的工具类

此外介绍一个更好用的姿势,直接使用开源项目 github.com/liuyueyi/qu… 来实现灰度处理

使用这个项目的 image-plugins 之后,生成一个灰度图就很简单了

1
2
3
4
5
6
7
8
9
10
java复制代码@Test
public void testImgGrayAlg() {
String img = "https://c-ssl.duitang.com/uploads/item/201809/16/20180916175034_Gr2hk.thumb.1000_0.jpeg";
BufferedImage out = ImgPixelWrapper.build()
.setSourceImg(img)
.setPixelType(PixelStyleEnum.GRAY_ALG)
.build()
.asBufferedImg();
System.out.println(out);
}

注意这个ImgPixelWrapper封装类,处理基础的灰度处理之外,还支持生成字符图,图片像素化(如马赛克…)

至于quick-media这个项目就更有意思了,java侧若想生成酷炫的二维码,选择它绝对不会让你失望;有兴趣的小伙伴可以瞅一瞅

一灰灰的联系方式

尽信书则不如无书,以上内容,纯属一家之言,因个人能力有限,难免有疏漏和错误之处,如发现bug或者有更好的建议,欢迎批评指正,不吝感激

  • 个人站点:blog.hhui.top
  • 微博地址: 小灰灰Blog
  • QQ: 一灰灰/3302797840
  • 微信公众号:一灰灰blog

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

Python数据分析:缺失值检测与处理

发表于 2021-11-12

「这是我参与11月更文挑战的第12天,活动详情查看:2021最后一次更文挑战」。

在实际的数据处理中,缺失值是普遍存在的,如何使用 Python 检测和处理缺失值,就是本文要讲的主要内容。

检测缺失值

我们先创建一个带有缺失值的数据框(DataFrame)。

1
2
3
4
5
6
7
8
python复制代码import pandas as pd

df = pd.DataFrame(
{'A': [None, 2, None, 4],
'B': [10, None, None, 40],
'C': [100, 200, None, 400],
'D': [None, 2000, 3000, None]})
df

数值类缺失值在 Pandas 中被显示为 NaN (Not A Number)。下面看看如何判断哪些列或者哪些行有缺失值。

1.info()

info() 返回的结果中,我们只需要观察每一列对应的 Non-Null Count 的数量是否等于 RangeIndex(索引范围) 即可。

2.isnull()

isnull() 返回一个与原 DataFrame 大小(列数,行数)相同的数据框,行列对应的数据代表着该位置是否为缺失值。

1
python复制代码df.isnull()


使用 sum() 来检测每列中的缺失值的数量。

1
python复制代码df.isnull().sum()


通过 .T 将 DataFrame 转置,获取检测每行中缺失值的数量。

1
python复制代码df.isnull().T.sum()

缺失值处理

删除缺失值

如果出现缺失值的行/列重要性不大的话,可以直接使用 dropna() 删除带有缺失值的行/列。

1
2
3
4
5
python复制代码df.dropna(axis=0,
how='any',
thresh=None,
subset=None,
inplace=False)

参数含义

  • axis:控制行列的参数,0 行,1 列。
  • how:any,如果有 NaN,删除该行或列;all,如果所有值都是 NaN,删除该行或列。
  • thresh:指定 NaN 的数量,当 NaN 数量达到才删除。
  • subset:要考虑的数据范围,如:删除缺失行,就用subset指定参考的列,默认是所有列。
  • inplace:是否修改原数据,True直接修改原数据,返回 None,False则返回处理后的数据框。

指定 axis = 1,如果列中有缺失值,则删除该列。

1
python复制代码df.dropna(axis=1, how='any')


由于每列都有缺失值,所以只剩索引。
指定 axis = 0(默认),如果行中有缺失值,则删除该行。

1
python复制代码df.dropna(axis=0, how='any')

以 ABC 列为参照,删除这三列都是缺失值的行。

1
python复制代码df.dropna(axis=0, subset=['A', 'B', 'C'], how='all')

保留至少有3个非NaN值的行。

1
python复制代码df.dropna(axis=0, thresh=3)

填补缺失值

另一种常见的缺失值处理方式就是使用 fillna() 填补缺失值。

1
2
3
4
5
python复制代码df.fillna(value=None,
method=None,
axis=0,
inplace=False,
limit=None)

1. 直接指定填充值

1
python复制代码df.fillna(666)


2.用缺失值前/后的值填充

按前一个值填充

当method 值为 ffill 或 pad时,按前一个值进行填充。

当 axis = 0,用缺失值同一列的上一个值填充,如果缺失值在第一行则不填充。

当 axis = 1,用缺失值同一行的上一个值填充,如果缺失值在第一列则不填充。

1
python复制代码df.fillna(axis=0, method='pad')


按后一个值填充

当method 值为 backfill 或 bfill时,按后一个值进行填充。

当 axis = 0,用缺失值同一列的下一个值填充,如果缺失值在最后一行则不填充。

当 axis = 1,用缺失值同一行的下一个值填充,如果缺失值在最后一列则不填充。

1
python复制代码df.fillna(axis=0, method='bfill')

指定相应的方法来填充

1
python复制代码df.fillna(df.mean())

limit限制填充次数

在ABCD列上,每列只填充第一个空值。

1
python复制代码df.fillna(value=666, axis=1, limit=1)


这就是今天要分享的内容,微信搜 Python新视野,每天带你了解更多有用的知识。更有整理的近千套简历模板,几百册电子书等你来领取哦!

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

Go语言 zap日志系统与gin继承

发表于 2021-11-12

基本演示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
typescript复制代码var logger *zap.Logger

func main() {
initLogger()
// 在程序退出之前把缓冲区里的文件都刷到硬盘上
defer logger.Sync()
httpGet("https://www.baidu.com")
httpGet("https://www.google.com")
}

func initLogger() {
// 这里还可以New Develp环境的日志 只是输出格式不太一样
logger, _ = zap.NewProduction()
}

func httpGet(url string) {
resp, err := http.Get(url)
if err != nil {
logger.Error("Error fetching url..", zap.String("url", url), zap.Error(err))
} else {
logger.Info("Success..", zap.String("statusCode", resp.Status), zap.String("url", url))
resp.Body.Close()
}
}

image.png

定制Logger

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
go复制代码
var logger *zap.Logger

func main() {
initLogger()
// 在程序退出之前把缓冲区里的文件都刷到硬盘上
defer logger.Sync()
httpGet("https://www.baidu.com")
httpGet("https://www.google.com")
}

func initLogger() {
writeSync := getLogWriter()
encoder := getEncoder()
// 第三个参数 可以 指定从哪个级别开始输出日志
core := zapcore.NewCore(encoder, writeSync, zapcore.ErrorLevel)
logger = zap.New(core)

}

// 返回一个json编码的格式 一般情况下都用这个
func getEncoder() zapcore.Encoder {
return zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig())
}

// 就是将日志写到何处
func getLogWriter() zapcore.WriteSyncer {
// 这里我们每次都是重新打开文件,你也可以用open和append来追加文件,免的每次写入文件 都会覆盖掉之前的
file, _ := os.Create("./test.log")
return zapcore.AddSync(file)
}

func httpGet(url string) {
resp, err := http.Get(url)
if err != nil {
logger.Error("Error fetching url..", zap.String("url", url), zap.Error(err))
} else {
logger.Info("Success..", zap.String("statusCode", resp.Status), zap.String("url", url))
resp.Body.Close()
}
}

image.png

这里的ts 代表时间戳,但是这个时间戳可读性是在是太差,当然也可以修改这个配置的

1
2
3
4
5
6
arduino复制代码// 返回一个json编码的格式 一般情况下都用这个
func getEncoder() zapcore.Encoder {
config := zap.NewProductionEncoderConfig()
config.EncodeTime = zapcore.ISO8601TimeEncoder
return zapcore.NewJSONEncoder(config)
}

image.png

我们甚至可以在日志中打印 是哪个方法 调用了日志所在的方法

image.png

只要修改如下的方法即可:

1
ini复制代码logger = zap.New(core,zap.AddCaller())

日志切割

如果一直向一个文件里面写入信息,文件会很快膨胀到xxx gb 这显然是无法忍受的,因此我们还需要日志切割归档功能

go get -u github.com/natefinch/lumberjack

1
2
3
4
5
6
7
8
9
10
go复制代码func getLogWriterByJack() zapcore.WriteSyncer {
logger := &lumberjack.Logger{
Filename: "./test.log",
MaxSize: 10,// 单位是mb
MaxBackups: 5,// 备份数量 切割文件之前 会把文件做备份
MaxAge: 30, // 备份天数
Compress: false, //默认不压缩
}
return zapcore.AddSync(logger)
}

写个测试函数测试一下

1
2
3
4
5
6
css复制代码for i := 0; i < 10000000; i++ {
//httpGet("https://www.baidu.com")
//httpGet("https://www.google.com")

logger.Info("i:")
}

image.png

在gin中 集成zap

这里类似的文章有很多 甚至也有人直接做好了类似的功能 github.com/gin-contrib…

有兴趣的可以体验下,很简单,就不再过多介绍了,有需要的话 大家记得取一下即可。

整体上来说 并不难,也就是实现对应的logger和recovery 而已,大家可以自行下载源码以后

稍微改动一下 然后来适配成自己的项目

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

Redis RDB和AOF持久化机制详解 概念 RDB RD

发表于 2021-11-12

「这是我参与11月更文挑战的第12天,活动详情查看:2021最后一次更文挑战」。

概念

Redis支持RDB和AOF两种持久化机制,持久化功能有效的避免因进程退出造成的数据丢失问题,当下次重启时利用之前持久化文件即可实现数据恢复

RDB

Redis提供RDB持久化功能,RDB持久化是把Redis 数在内存中的数据生成快照保存到硬盘的过程,触发RDB持久化过程分为手动触发和自动触发。

RDB持久化生成的RDB文件是一个经过压缩的二进制文件,通过这个文件可以还原生成RDB时数据库当时的完整数据,从而实现数据持久化,避免数据丢失。

RDB文件的创建和载入

RDB文件创建基本概念

有两个Redis命令可以用于生成RDB文件,一个是SAVE,另一个是BGSAVE。

SAVE命令会阻塞Redis服务进程,直到RDB文件创建完毕为止,在服务器进程阻塞期间,服务器不能处理任何命令请求。

image.png

当SAVE命令正在执行时,客户端发送的所有命令请求都会被拒绝,只有在服务器执行完SAVE命令,重新开始接受命令请求之后,客户端发送的命令才会被处理。

BGSAVE命令不会阻塞服务器进程,BGSAVE命令会派生出一个子进程,然后由子进程负责创建RDB文件,服务器进程继续处理命令请求,在BGSAVE命令执行期间,客户端发送的SAVE,BGSAVE命令都会被拒绝。

image.png

创建RDB文件实际是由rdbsave函数来完成,save和bgsave命令会以不同的方式调用这个函数。

文件载入

RDB文件载入是在服务器启动时自动执行的,只要Redis服务器在启动时检测到RDB文件存在,就会自动载入到RDB文件。我们来看下Redis启动时打印的日志文件

image.png

DB loaded from disk 就是服务器在成功载入RDB文件之后打印的。

这里要强调一下,因为AOF文件更新频率通常比RDB文件更新频率高,所以如果服务器开启了AOF持久化功能,那么服务器会优先使用AOF文件来还原数据,只有在AOF关闭时,才会使用RDB文件来还原数据。

载入RDB文件实际是由rdbload函数来完成。

服务器在载入RDB文件期间,会一直处于阻塞状态,直到载入工作完成。

自动保存

因为BGSAVE命令不会阻塞主服务器进程,所以Redis允许用户通过设置服务器配置的save选项,让服务器自动每隔一段时间执行一次BGSAVE命令。

image.png

  • 服务器在900秒之内对数据库进行了至少一次修改。
  • 服务器在300秒之内对数据库进行了至少10次修改。
  • 服务器在60秒之内,对数据库进行了至少10000次修改。

只要满足这三个条件的任意一个,BGSAVE命令就会被执行。

如果用户没有主动设置save选项,服务器就会为save选项设置默认条件,上图展示的就是默认条件。

自动保存

服务器会根据save选项设置的保存条件,设置服务器状态redisServer结构的saveparams属性

image.png

saveparams属性是一个数组,数组中的每个元素都是一个saveparam结构,每个saveparam结构都保存了一个save选项设置的保存条件。

image.png

除了saveparams数组之外,服务器状态还维持着一个dirty计数器和一个lastsave属性。

dirty计数器用来记录距离上一次成功执行SAVE命令或者BGSAVE命令之后,服务器对数据库状态进行了多少次修改。

lastsave属性是一个UNIX时间撮,记录了服务器上一次成功执行SAVE命令或者BGSAVE命令的时间。

服务器每成功执行一次数据库修改命令,dirty计数器会进行更新,修改多少次数据库dirty值就会增加多少。

Redis的服务器周期性操作函数serveCron默认每隔100毫秒执行一次,会检查save选项设置的保存条件是否已经满足,如果满足,就执行BGSAVE命令。

RDB文件结构

image.png

RDB文件基本上就是由这五个部分组成。

  • REDIS:RDB文件开头是REDIS部分,保存着”REDIS”五个字符。通过这5个字符,程序在载入文件时,可以快速检查所载入的文件是否是RDB文件。
  • db_vsersion长度是4字节,它的值是一个字符串表示的整数。记录了RDB文件到版本号。
  • databases:包含着零个或多个数据库,以及各个数据库中的键值对数据。
  • EOF常量的长度为1字节,这个常量标志着RDB文件正文内容的结束,当程序读入这个值,就代表所有键值对数据已经加载完毕。
  • check_sum是一个8字节长的整数,保存着一个校验和,这个校验和是程序通过对REDIS,db_version,databases、EOF四个部分的内容进行计算得出的。服务器载入RDB文件时会将载入数据时计算的校验和与check_sum进行对比,来检查RDB文件是否有出错。

image.png

databases部分

这里重点说下databases部分,如果服务器的0号数据库和3号数据库非空,那么服务器将创建一个下图所示的RDB文件

image.png

每个非空的数据库都是由SELECTDB、db_number、key_value_pairs三个部分组成

image.png

  • SELECTDB:长度为1字节,当程序遇到这个值,就知道接下来要读入的将是一个数据库号码
  • db_number:保存着一个数据库号码,当程序读入这个db_number,服务器会调用SELECT命令,根据读入的数据库号码进行数据库切换,使得之后读入的键值对可以载入到正确的数据库中。
  • key_value_pairs:保存了数据库的所有键值对数据,如果键值对带有过期时间,这个过期时间也会和键值对保存在一起。

不带过期时间的键值对在RDB文件中由TYPE、key、value三部分组成

带过期时间的键值对由EXPIRETIME_MS,ms,type,key,value组成。

type记录了value的类型,EXPIRETIME_MS是个常量,1字节,它告知程序接下来要读入的是一个以毫秒为单位的过期时间。

ms是一个8字节长的带符号整数记录了一个亿毫秒为单位的时间戳。

回顾

image.png

AOF

AOF持久化是通过保存Redis服务器所执行的写命令来记录数据库状态。

AOF持久化实现

命令追加

当AOF持久化功能处于打开状态时,服务器在执行完一个写命令后,会以Redis的协议格式将被执行的写命令追加到服务器中的aof_buf缓冲区的末尾。

写入与同步

Redis的服务器进程是一个事件循环,在服务器每次结束一个事件循环之前,它都会调用flushAppendOnlyFile函数,考虑是否需要将aof_buf缓冲区的内容写入和保存到AOF文件里面。

flushAppendOnlyFile函数的行为由服务器配置的appendfsync选项的值来决定

image.png

always:命令写入aof_buf后调用系统fsync操作同步到AOF文件,fsync完成后线程返回  

everysec:命令写入aof_buf后调用系统write操作,write完成后线程返回。fsync同步文件操作由专门线程每秒调用一次  

no:命令写入aof_buf后调用系统write操作,不对AOF文件做fsync同步,同步硬盘操作由操作系统负责,通常同步周期最长30s  

配置为always时,每次写入都要同步AOF文件,在一般的SATA硬盘上,Redis只能支持大约几百TPS写入,显然跟Redis高性能特性背道而驰,不建议配置。  

配置为no,由于操作系统每次同步AOF文件的周期不可控,而且会加大每次同步硬盘的数据量,虽然提升了性能,但数据安全性无法保证。   

配置为everysec,是建议的同步策略,也是默认配置,做到兼顾性能和数据安全性。理论上只有在系统突然宕机的情况下丢失1秒的数据。

重写

随着命令不断写入,会导致aof文件越来越大,文件体积会越来越大,体积过大的AOF文件很可能对Redis服务器甚至整个计算机造成影响,为了解决这个问题,Redis提供了AOF文件重写功能,通过该功能,Redis服务器会创建一个新的AOF文件来替代现有AOF文件,新的AOF文件不会包含任何浪费空间的冗余命令,所以新的AOF文件体积会比旧AOF文件体积要小的多。

重写具体实现

AOF文件重写不需要对现有AOF文件进行任何读取,分析或者写入操作,这个功能是通过读取服务器的数据库内容来实现,考虑以下情景

image.png

为了记录animals键值,AOF文件必须保存上面列出的四条命令。

而当AOF文件重写时,服务器会直接通过读取当前animals键的值,生成一条sadd animals “Dog” “Panda” “Tiger” “Lion” “Cat” 命令来代替上面的四条命令,这样就将保存animals键所需命令从四条减少到一条了。

AOF后台重写

AOF重写会进行大量写入操作,所以为了避免因为重写造成服务器被阻塞,Redis服务器会使用子进程来进行执行,子进程进行AOF重写期间,服务器进程可以继续处理命令请求。

使用子进程也会存在一个问题,因为子进程重写期间,服务器进程会继续处理命令请求,新的命令会对现有数据库内容进行修改,从而造成当前数据库内容和重写后的AOF文件内容不一致。

为了解决这个问题,Redis服务器设置了AOF重写缓冲区,这个缓冲区在服务器创建子进程之后开始使用,当Redis服务器执行完一个写命令后,它会同时将这个写命令发送给AOF缓冲区和AOF重写缓冲区。

从创建为AOF重写的子进程开始,服务器执行的所有写命令都会被记录到AOF重写缓冲区中。

当子进程完成AOF重写之后,会向服务器进程发送一个信号,服务器进程接收到这个信号会做两件事

  • 将AOF重写缓冲区的所有内容写入到新的AOF文件
  • 将新的AOF文件进行改名,原子性的覆盖旧的AOF文件

image.png

回顾

image.png

RDB和AOF对比

image.png

  • RDB文件的载入工作是在服务器启动时自动执行的,并没有专门的命令。但是由于AOF的优先级更高,因此当AOF开启时,Redis会优先载入AOF文件来恢复数据;只有当AOF关闭时,才会在Redis服务器启动时检测RDB文件,并自动载入。服务器载入RDB文件期间处于阻塞状态,直到载入完成为止。

事务

事务主要的几个命令

  1. multi

事务命令执行的开始,当执行这个命令后,接下来所有的redis命令会存入一个队列当中,不会立即执行,只有当执行exec后,所有的命令会按照进入队列的命令先后执行

  1. exec

事务命令的结束,当执行这个命令时,会把先前进入队列的命令一一执行

3.watch

watch 命令是给键加监控,如果在当前事务中,该键发送了更改,则最终就不会去执行。会返回空

4.DISCARD

清除所有先前在事务中的命令

  1. unwatch

清除所有先前监控的键

示例

image.png

image.png

watch命令原理

带有watch命令的事务会将客户端和被监控的键在数据库中的watched_keys字典中进行管理,当键被修改时,程序会将所有监视被修改键的客户端的redis_dirty_cas 标志打开。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

1…360361362…956

开发者博客

9558 日志
1953 标签
RSS
© 2025 开发者博客
本站总访问量次
由 Hexo 强力驱动
|
主题 — NexT.Muse v5.1.4
0%