工作纪实2020

每日一思篇

[2019-10-12 每日一思] Mysql WAL技术 和 RingBuffer 思想好一致?

[2019-10-14 每日一思] JWT 续签该如何做?

[2019-10-16 每日一思] TCP/IP 协议具体指哪些?

我们都知道网络是7层模型,应表会传网数物, 现在我只讨论应传网数这4层。TCP/IP协议应该被称为TCP/IP族, 我的理解他不是属于单个的协议类型,是一个统称,知道网络模型核心设计思想是分层,为什么分层,分层从设计上和实现难度上都简单很多,哪一层需要修改只需要修改这一层。

  • 应用层,像最常见的http、ftp、dns、rtsp、rtmp等等协议都是属于这类,
  • 传输层呢按照传输类型又分了TCP和UDP,
  • 网络层,是数据包交互的层面,
  • 数据链路层是处理网卡、操作系统等等软硬抽象出的可见部分。

举一个栗子,一个http请求,在应用层面是完整的,后面被传输层(TCP层)被分包,并打上序号标记,再进入网络层(IP层)添加IP首部(目标mac地址等等), 下面就是开始疯狂的发送了,接收方一样是这个过程的逆序。应用处理完成后面的响应过程与请求过程一样的一个过程。同时可以扩展出L4与L7的问题, 各自是如何去实现负载均衡的?L4是可以看出是基于传输层即TCP层工作(通过发布VIP(第三层)以及第四层端口),L7基于应用层工作(第四层基础上+考虑应用特征), 比如HTTP的URL、客户端的类别、语言类型等等。

[2019-10-18 每日一思]一种场景,rabbitmq 的 Exchange 为 fanout 类型,绑定到多个queue, 什么情况会触发 rabbitmq 流控?如何解决?

[2019-10-22 每日一思]ID序列生产器怎么实现呢?

uuid生成

  • 基于时间(60位utc时间 和 时间序列值14位,以及mac地址)
  • 基于名称(针对命名空间dns、url等分配,把名称转成字节序列,再用md5或sha-1与命名空间标识进行计算,产生哈希结果)
  • 基于随机数(密码学随机数,系统的硬盘内存线程堆栈进程句柄等sha-1生成哈希结果)

snowflake,64bit,long型ID

  • ID生成方式 1bit(不使用),41bit时间戳(当前毫秒数、69年一轮回),10bit机器码(1024台,5bit数据中心,5bit机器ID),12bit作为毫秒内序列号(单机理论 409.6w/s)
  • 雪花算法,多台机器,有因为时钟回拨导致的ID生成问题,当然可以通过发生时钟回拨后一个阈值,在阈值内则不允许产生新的ID,同步阻塞,在阈值外重新设置机器ID来解决
  • github.com/baidu/uid-generator 技术老铁百度开源的基于snowflake实现的ID生成器,可以借鉴研读一下

[2019-11-01 每日一思]我们常说的限流是什么?为什么要限流?限流有哪些方式?

我们常说的限流,顾名思义即限制流量. 限制系统的输入和输出 常用的限流发展至今,有四种方式

  • 固定时间计数器
  • 漏桶
  • 令牌桶
  • 滑动窗口计数器

固定窗口计数器:以单位时间内进入系统(系统级别)或者某一个单一接口服务(系统服务级别)请求次数,在这个单位时间内的超过次数,拒绝服务或者更换其他方案(降级、熔断)达到限流目的。

可以看出,明显的缺点,从整体曲线上,毛刺现象非常严重,假设单位时间 1s 内限制 100 次,在0-10ms内我已经请求超过100次了,后面的请求全部拒绝或者做其他处理了。无法控制单位时间内的突发流量。

漏桶: 桶的容量固定,桶流出的速率恒定。桶满则限流。也是无法应对突发流量

令牌桶: 还是桶的方式, 桶中存放的是 token ,根据限流的大小, token 以恒定速率进入桶中,设置桶的最大的 token 容量,当桶满时候拒绝新添加的token ,或者直接丢弃。所有请求进入先获取令牌,得到令牌继续下面的业务逻辑,处理完成删除 token。

相比 漏桶, 一定程度上允许突发流量,平滑限流,因为 桶 中的 token 是匀速放入的,抵御突发流量。桶的 token 数量不会超过给定的最大值。 参考 guava rate limiter

滑动窗口计数器:(参考Sentinel中使用的默认限流方式) 是对固定时间窗口计数器的优化,就是为了解决固定时间计数器的无法面对突发流量,何为滑动窗口呢? 假设单位时间定位 1s, 计数器限制在 1000 次, 再将者 1s 划分为 100ms 为一个小格子,总共 10 个格子, 每个格子还有计数器,当一个请求进来,在对应的时间格子里面的计数器 +1 ,只要总的这个单位时间内所有的时间格子计数器总和小于 限制次数,就不会启动限流作用。 可以看出来,假如我的划分的格子很小,滑动窗口滚动将是越平滑的。 ps: 0.00 - 1.00 有 10 个格子,这是一个滑动窗口期,然后 0.10-1.10是一个窗口期,这样计算的

[2019-11-04 每日一思] 假如MQ消息队列产生了堆积,如何更快的消费?

会从多种队列去分析,rabbitmq,kafka,rocketmq,pulsar等多个消息中间件去分析。 RabbitMq得看是什么模式,如果是生产消费者模式是可以的,发布订阅模式就没卵用,加个订阅者而已。kafka看consumer的数量吧,如果已经大于等于partition数量了,加consumer也没卵用。pulsar 是也一样啊,有生产者消费者模式,也有独占和失败替补模式。

对的,先说kafka和RocketMQ,他们都是基于partition的存储模型,也就是说每个subject分为一个或多个partition,Server收到消息分发到某个partition上,而consumer 消费时候是与partition相对应的,当partition与consumer数量相等时,是一对一,当小于时候,会有consumer空闲,大于时候,看数量,有consumer 会负责多个,比较繁忙。在产生队列积压时候,这时候最合理的分配减压策略是 partition数量和consumer数量成倍数关系,单个增加consumer 数量并不能有效提高时候,并且并不能马上提高消费效率,需要添加对应的partition数量,但是就带来了另外的问题,partition数量增加,特别是kafka,partition资源是一个比较重的资源,增加partition 数量还需要考虑集群的处理能力,另外在高峰过后,想要consumer缩容也比较麻烦哦,因为partition只能增加不能减少。

针对partition存储方式的,扩容相关的问题,已经堆积的消息是不能快速消费的,假设是2partition对2个consumer,这时候增加partition和consumer是无用的,因为已经堆积的只能由这两个consumer 消费,横向扩展不可能了,只能纵向扩展,这时候要么只能接受已经堆积慢慢消费,并且尽量减少入这两个partition的消息,或者执行比较重点再均衡策略!

[2019-11-18 每日一思] HttpDNS有啥好处, HttpDNS 为什么一般只适用于 APP 或者 C/S 架构,B/S 为何不适用?#

HttpDNS 是基于HTTP协议发起服务器A记录的地址,不存在向本地运营商询问 domain 解析过程 适用于APP或者C/S架构,是它的特殊性导致的,

  • 一般在终端这种信号差、信号不稳定下,要想尽量的介绍交互
  • 跑马机制选取就近地址
  • SDK嵌入

[2019-12-12 每日一思]我们知道许多长链接情况下,保证链接有效大部分是通过 TCP Keepalive 与 应用心跳 来确定,各自的定位是什么?为什么大多数情况下很多IM系统同时使用 TCP Keepalive 与 应用层心跳去确认? 应用层心跳设计上有啥讲究?

TCP Keepalive 是 TCP/IP 协议自带,无需额外的开发,但是不灵活,而且我们一般在应用层面是无法感知。更改TCP Keepalive 相关参数需要修改/etc/sysctl.conf

应用层心跳是为了保活,保证链接的可用性

  • 运营商的环境,,存在 NAT 超时断链问题
  • 让应用知晓网络可用情况,酌情处理断链重连问题
  • 可以有效的江都服务端为了维护无效链接的开销

应用层心跳设计上

  • 固定频率,通常只有请求头,消息体空包(在判断这类心跳处理时候,客户端一般要判断超时时间大于心跳间隔【可能存在网络延迟】)
  • 智能心跳,根据网络状况自动调整发送的频率,来适配当前网络情况的最佳心跳频率(各个地区各个服务商 NAT 超时设计不一样,长的多达几个小时,少的只有几秒)

智能心跳,一般采用二分法来逐渐逼近服务商的 NAT 超时设计。心跳设计上不会是空包,而是传输的是客户端的心跳频率是多少。 (假设 最小值 20 s 最大值 1小时)===> 逐渐二分下去

[2019-12-14 每日一思] 如何判断任意用户上传的文件在服务端是否存在呢?网盘的“秒传”的实现思路?

对比文件,确实是使用的文件的提取的特征值(单项哈希算法),哈希算法是有冲突的,虽然概率较小, 那么如何这种冲突呢:在服务端, 对该文件进行不同的单向Hash算法(MD5,SHA-1)等等得到多个值,一一对比,只有全部相同才认为是同一个文件。

为什么要哈希?为了将一个不定长、不确定的东东 转化成 固定长度的固化的特征值,并要保证唯一性。

填坑记录

docker相关

  • docker默认网桥问题

服务器默认网桥 172.19.0.0, 而 docker 网桥默认172.17.0.0, 在启动多个的docker容器的时候会导致地址冲突,桥接网卡凉, 需要更改docker 默认的网桥地址

 { 
   "debug" : true,
   "default-address-pools" : [
       {
           "base" : "172.31.0.0/16",
          "size" : 24
       }
   ]
 }

更改成和主机不一样的的网段地址

redis分布式锁


package com.kezaihui.thor.biz.core.adapter.redis.lock;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Component;

import java.util.Collections;

/**
 * RedisTemplateDistributeLockUtil
 */
@Slf4j
@Component
public class RedisTemplateDistributeLockUtil {


    public static final String LOCK_SCRIPT =
            "local key = KEYS[1]; local value = ARGV[1]; if redis.call('set', key, value, 'NX' ,'PX', %d) "
                    + "then return 1 else return 0 end";

    public static final String UNLOCK_SCRIPT =
            "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end";

    public static final int EXPIRE = 5000;

    @Autowired
    private RedisTemplate redisTemplate;


    private String lockExpireScript(int expireMills) {
        if (expireMills <= 0) {
            expireMills = EXPIRE;
        }
        return String.format(LOCK_SCRIPT, expireMills);
    }

    public boolean lock(String key, String value, int expireMills) {
        String script = lockExpireScript(expireMills);
        DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>(script, Long.class);
        Long execute = (Long) redisTemplate.execute(redisScript, Collections.singletonList(key),
                Collections.singletonList(value));
        return execute != null && execute != 0;
    }

    public boolean blockLock(String key, String value, int expireMills) throws DistributeLockTimeoutException {
        // 被阻塞的时间超过5秒就停止获取锁
        int blockTime = expireMills;
        // 默认的间隔时间
        for (; ; ) {
            if (blockTime >= 0) {
                String script = lockExpireScript(expireMills);
                DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>(script, Long.class);
                Long result = (Long) redisTemplate.execute(redisScript, Collections.singletonList(key), value);
                if (result != null && result == 1) {
                    // 得到了锁
                    return true;
                } else {
                    blockTime -= 300;
                    try {
                        Thread.sleep(300);
                    } catch (InterruptedException e) {
                        log.error("RedisTemplateDistributeLockUtil.blockLock error!", e);
                    }
                }
            } else {
                // 已经超时
                throw new DistributeLockTimeoutException("Distribute Lock Timeout");
            }
        }
    }

    public boolean unlock(String key, String value) {
        String script = UNLOCK_SCRIPT;
        DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>(script, Long.class);
        Long execute = (Long) redisTemplate.execute(redisScript, Collections.singletonList(key), value);
        return execute != null && execute != 0;
    }
}
comments powered by Disqus