Nicksxs's Blog

What hurts more, the pain of hard work or the pain of regret?

0%

redis过期策略复习

之前其实写过redis的过期的一些原理,这次主要是记录下,一些使用上的概念,主要是redis使用的过期策略是懒过期和定时清除,懒过期的其实比较简单,即是在key被访问的时候会顺带着判断下这个key是否已过期了,如果已经过期了,就不返回了,但是这种策略有个漏洞是如果有些key之后一直不会被访问了,就等于沉在池底了,所以需要有一个定时的清理机制,去从设置了过期的key池子(expires)里随机地捞key,具体的策略我们看下官网的解释

  1. Test 20 random keys from the set of keys with an associated expire.
  2. Delete all the keys found expired.
  3. If more than 25% of keys were expired, start again from step 1.

从池子里随机获取20个key,将其中过期的key删掉,如果这其中有超过25%的key已经过期了,那就再来一次,以此保持过期的key不超过25%(左右),并且这个定时策略可以在redis的配置文件

# Redis calls an internal function to perform many background tasks, like
# closing connections of clients in timeout, purging expired keys that are
# never requested, and so forth.
#
# Not all tasks are performed with the same frequency, but Redis checks for
# tasks to perform according to the specified "hz" value.
#
# By default "hz" is set to 10. Raising the value will use more CPU when
# Redis is idle, but at the same time will make Redis more responsive when
# there are many keys expiring at the same time, and timeouts may be
# handled with more precision.
#
# The range is between 1 and 500, however a value over 100 is usually not
# a good idea. Most users should use the default of 10 and raise this up to
# 100 only in environments where very low latency is required.
hz 10

可以配置这个hz的值,代表的含义是每秒的执行次数,默认是10,其实也用了hz的普遍含义。有兴趣可以看看之前写的一篇文章redis系列介绍七-过期策略

又到半年总结时,第一次写总结类型的文章感觉挺好写的,但是后面总觉得这过去的一段时间所做的事情,能力上的成长低于预期,但是是需要总结下,找找问题,顺便展望下未来。

这一年做的最让自己满意的应该就是看了一些书,由折腾群洋总发起的读书打卡活动,到目前为止已经读完了这几本书,《cUrl 必知必会》,《古董局中局 1》,《古董局中局 2》,《算法图解》,《每天 5 分钟玩转 Kubernetes》《幸福了吗?》《高可用可伸缩微服务架构:基于 Dubbo、Spring Cloud和 Service Mesh》《Rust 权威指南》后面可以写个专题说说看的这些书,虽然每天打卡如果时间安排不好,并且看的书像 rust 这样比较难的话还是会有点小焦虑,不过也是个调整过程,一方面可以在白天就抽空看一会,然后也不必要每次都看很大一章,注重吸收。

技术上的成长的话,有一些比较小的长进吧,对于一些之前忽视的 synchronized,ThreadLocal 和 AQS 等知识点做了下查漏补缺了,然后多了解了一些 Java 垃圾回收的内容,但是在实操上还是比较欠缺,成型的技术方案,架构上所谓的优化也比较少,一些想法也还有考虑不周全的地方,还需要多花时间和心思去学习加强,特别是在目前已经有的基础上如何做系统深层次的优化,既不要是鸡毛蒜皮的,也不能出现一些不可接受的问题和故障,这是个很重要的课题,需要好好学习,后面考虑定一些周期性目标,两个月左右能有一些成果和总结。

另外一部分是自己的服务,因为 ucloud 的机器太贵就没续费了,所以都迁移到腾讯云的小机器上了,顺便折腾了一点点 traefik,但是还很不熟练,不太习惯这一套,一方面是 docker 还不习惯,这也加重了对这套环境的不适应,还是习惯裸机部署,另一方面就是 k8s 了,家里的机器还没虚拟化,没有很好的条件可以做实验,这也是读书打卡的一个没做好的点,整体的学习效果受限于深度和实操,后面是看都是用 traefik,也找到了一篇文章可以 traefik 转发到裸机应用,因为主仓库用的是裸机的 gogs。

还有就是运动减肥上,唉,这又是很大的一个痛点,基本没效果,只是还算稳定,昨天看到一个视频说还需要力量训练来增肌,以此可以提升基础代谢,打算往这个方向尝试下,因为今天没有疫情限制了,在 6 月底完成了 200 公里的跑步小目标,只是有些膝盖跟大腿根外侧不适,抽空得去看下医生,后面打算每天也能做点卷腹跟俯卧撑。

下半年还希望能继续多看看书,比很多网上各种乱七八糟的文章会好很多,结合豆瓣评分,找一些评价高一些的文章,但也不是说分稍低点的就不行,有些也看人是不是适合,一般 6 分以上评价比较多的就可以试试。

springboot 自动装配调用链

springboot 相比 spring能更方便开发人员上手,比较重要的一点就是自动装配,大致来看下这个逻辑

public static void main(String[] args) {
		SpringApplication.run(SpbDemoApplication.class, args);
	}

	/**
	 * Static helper that can be used to run a {@link SpringApplication} from the
	 * specified source using default settings.
	 * @param primarySource the primary source to load
	 * @param args the application arguments (usually passed from a Java main method)
	 * @return the running {@link ApplicationContext}
	 */

然后就是上面调用的 run 方法

public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
	return run(new Class<?>[] { primarySource }, args);
}

/**
 * Static helper that can be used to run a {@link SpringApplication} from the
 * specified sources using default settings and user supplied arguments.
 * @param primarySources the primary sources to load
 * @param args the application arguments (usually passed from a Java main method)
 * @return the running {@link ApplicationContext}
 */

继续往下看

public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
	return new SpringApplication(primarySources).run(args);
}
Read more »

题目介绍

给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。

示例


输入:height = [0,1,0,2,1,0,1,3,2,1,2,1]
输出:6
解释:上面是由数组 [0,1,0,2,1,0,1,3,2,1,2,1] 表示的高度图,在这种情况下,可以接 6 个单位的雨水(蓝色部分表示雨水)。

简单分析

其实最开始的想法是从左到右扫区间,就是示例中的第一个水槽跟第二个水槽都可以用这个办法解决

前面这种是属于右侧比左侧高的情况,对于左侧高右侧低的就不行了,(写这篇的时候想起来可以再反着扫一遍可能可以)

所以这个方案不好,贴一下这个方案的代码

public int trap(int[] height) {
    int lastLeft = -1;
    int sum = 0;
    int tempSum = 0;
    boolean startFlag = true;
    for (int j : height) {
        if (startFlag && j <= 0) {
            startFlag = false;
            continue;
        }
        if (j >= lastLeft) {
            sum += tempSum;
            tempSum = 0;
            lastLeft = j;
        } else {
            tempSum += lastLeft - j;
        }
    }
    return sum;
}

后面结合网上的解法,其实可以反过来,对于每个格子找左右侧的最大值,取小的那个和当前格子的差值就是这一个的储水量了

理解了这种想法,代码其实就不难了

代码

int n = height.length;
if (n <= 2) {
    return 0;
}
// 思路转变下,其实可以对于每一格算储水量,算法就是找到这一格左边的最高点跟这一格右边的最高点,
// 比较两侧的最高点,取小的那个,然后再跟当前格子的高度对比,差值就是当前格的储水量
int maxL[] = new int[n];
int maxR[] = new int[n];
int max = height[0];
maxL[0] = 0;
// 计算左侧的最高点
for (int i = 1; i < n - 1; i++) {
    maxL[i] = max;
    if (max < height[i]) {
        max = height[i];
    }
}
max = height[n - 1];
maxR[n - 1] = 0;
int tempSum, sum = 0;
// 计算右侧的最高点,并且同步算出来储水量,节省一个循环
for (int i = n - 2; i > 0; i--) {
    maxR[i] = max;
    if (height[i] > max) {
        max = height[i];
    }
    tempSum = Math.min(maxL[i], maxR[i]) - height[i];
    if (tempSum > 0) {
        sum += tempSum;
    }
}
return sum;

Java并发

synchronized 的一些学习记录

jdk1.6 以后对 synchronized 进行了一些优化,包括偏向锁,轻量级锁,重量级锁等

这些锁的加锁方式大多跟对象头有关,我们可以查看 jdk 代码

首先对象头的位置注释

// Bit-format of an object header (most significant first, big endian layout below):
//
//  32 bits:
//  --------
//             hash:25 ------------>| age:4    biased_lock:1 lock:2 (normal object)
//             JavaThread*:23 epoch:2 age:4    biased_lock:1 lock:2 (biased object)
//             size:32 ------------------------------------------>| (CMS free block)
//             PromotedObject*:29 ---------->| promo_bits:3 ----->| (CMS promoted object)
//
//  64 bits:
//  --------
//  unused:25 hash:31 -->| unused:1   age:4    biased_lock:1 lock:2 (normal object)
//  JavaThread*:54 epoch:2 unused:1   age:4    biased_lock:1 lock:2 (biased object)
//  PromotedObject*:61 --------------------->| promo_bits:3 ----->| (CMS promoted object)
//  size:64 ----------------------------------------------------->| (CMS free block)
//
//  unused:25 hash:31 -->| cms_free:1 age:4    biased_lock:1 lock:2 (COOPs && normal object)
//  JavaThread*:54 epoch:2 cms_free:1 age:4    biased_lock:1 lock:2 (COOPs && biased object)
//  narrowOop:32 unused:24 cms_free:1 unused:4 promo_bits:3 ----->| (COOPs && CMS promoted object)
//  unused:21 size:35 -->| cms_free:1 unused:7 ------------------>| (COOPs && CMS free block)
enum { locked_value             = 0,
         unlocked_value           = 1,
         monitor_value            = 2,
         marked_value             = 3,
         biased_lock_pattern      = 5
};

我们可以用 java jol库来查看对象头,通过一段简单的代码来看下

public class ObjectHeaderDemo {
    public static void main(String[] args) throws InterruptedException {
        L l = new L();
        System.out.println(ClassLayout.parseInstance(l).toPrintable());
		}
}

Untitled

然后可以看到打印输出,当然这里因为对齐方式,我们看到的其实顺序是反过来的,按最后三位去看,我们这是 001,好像偏向锁都没开,这里使用的是 jdk1.8,默认开始偏向锁的,其实这里有涉及到了一个配置,jdk1.8 中偏向锁会延迟 4 秒开启,可以通过添加启动参数 -XX:+PrintFlagsFinal,看到

偏向锁延迟

因为在初始化的时候防止线程竞争有大量的偏向锁撤销升级,所以会延迟 4s 开启

我们再来延迟 5s 看看

public class ObjectHeaderDemo {
    public static void main(String[] args) throws InterruptedException {
				TimeUnit.SECONDS.sleep(5);
        L l = new L();
        System.out.println(ClassLayout.parseInstance(l).toPrintable());
		}
} 

https://gitee.com/nicksxs/images/raw/master/uPic/2LBKpX.jpg

可以看到偏向锁设置已经开启了,我们来是一下加个偏向锁

public class ObjectHeaderDemo {
    public static void main(String[] args) throws InterruptedException {
        TimeUnit.SECONDS.sleep(5);
        L l = new L();
        System.out.println(ClassLayout.parseInstance(l).toPrintable());
        synchronized (l) {
            System.out.println("1\n" + ClassLayout.parseInstance(l).toPrintable());
        }
        synchronized (l) {
            System.out.println("2\n" + ClassLayout.parseInstance(l).toPrintable());
        }
		}
}

看下运行结果

https://gitee.com/nicksxs/images/raw/master/uPic/V2l78m.png

可以看到是加上了 101 = 5 也就是偏向锁,后面是线程 id

当我再使用一个线程来竞争这个锁的时候

public class ObjectHeaderDemo {
    public static void main(String[] args) throws InterruptedException {
        TimeUnit.SECONDS.sleep(5);
        L l = new L();
        System.out.println(ClassLayout.parseInstance(l).toPrintable());
        synchronized (l) {
            System.out.println("1\n" + ClassLayout.parseInstance(l).toPrintable());
        }
				Thread thread1 = new Thread() {
            @Override
            public void run() {
                try {
                    TimeUnit.SECONDS.sleep(5L);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (l) {
                    System.out.println("thread1 获取锁成功");
                    System.out.println(ClassLayout.parseInstance(l).toPrintable());
                    try {
                        TimeUnit.SECONDS.sleep(5L);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        };
				thread1.start();
		}
}

https://gitee.com/nicksxs/images/raw/master/uPic/bRMvlR.png

可以看到变成了轻量级锁,在线程没有争抢,只是进行了切换,就会使用轻量级锁,当两个线程在竞争了,就又会升级成重量级锁

public class ObjectHeaderDemo {
    public static void main(String[] args) throws InterruptedException {
        TimeUnit.SECONDS.sleep(5);
        L l = new L();
        System.out.println(ClassLayout.parseInstance(l).toPrintable());
        synchronized (l) {
            System.out.println("1\n" + ClassLayout.parseInstance(l).toPrintable());
        }
        Thread thread1 = new Thread() {
            @Override
            public void run() {
                try {
                    TimeUnit.SECONDS.sleep(5L);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (l) {
                    System.out.println("thread1 获取锁成功");
                    System.out.println(ClassLayout.parseInstance(l).toPrintable());
                    try {
                        TimeUnit.SECONDS.sleep(5L);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        };
        Thread thread2 = new Thread() {
            @Override
            public void run() {
                try {
                    TimeUnit.SECONDS.sleep(5L);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (l) {
                    System.out.println("thread2 获取锁成功");
                    System.out.println(ClassLayout.parseInstance(l).toPrintable());
                }
            }
        };
        thread1.start();
        thread2.start();
    }
}

class L {
    private boolean myboolean = true;
}

https://gitee.com/nicksxs/images/raw/master/uPic/LMzMtR.png

可以看到变成了重量级锁。