
在开发过程中,获取当前机器的 IP 地址是一个非常常见的需求。无论是应用部署时需要知道服务监听的地址,还是在开发测试中需要确认应用的访问地址,都需要可靠地获取当前机器的 IP 地址信息。
Java 标准库中提供
InetAddress
类来获取 IP 地址,但使用起来还是比较繁琐的。而 Hutool 作为一个强大的 Java 工具集,其中就提供非常便捷的 IP 地址获取方法。接下来,我们就详细探讨一下如何使用 Hutool 获取当前机器的所有 IP 地址,以及如何区分 IPv4 和 IPv6 地址。
Hutool 提供
NetUtil.getLocalIpv4s()
和
NetUtil.getLocalIpv6s()
两个方法,分别用于获取当前机器的所有 IPv4 和 IPv6 地址。我们可以将这两个方法的调用组合起来,就可以获取所有的 IP 地址。示例代码如下:
List<String> ipv4List = NetUtil.getLocalIpv4s();List<String> ipv6List = NetUtil.getLocalIpv6s();// 合并 IPv4 和 IPv6 地址List<String> allIpList = new ArrayList<>(ipv4List);allIpList.addAll(ipv6List);System.out.println("所有 IP 地址列表: " + allIpList);
通过这段代码,我们可以获取当前机器的所有 IP 地址,包括 IPv4 和 IPv6 地址。需要注意的是,机器没有配置 IPv6 地址,那么
NetUtil.getLocalIpv6s()
方法会返回一个空列表。
有时我们可能只需要获取特定类型的 IP 地址,比如只需要 IPv4 地址或只需要 IPv6 地址。这时,我们可以使用 Hutool 提供的
NetUtil.isIpv4(String ip)
和
NetUtil.isIpv6(String ip)
方法来判断 IP 地址的类型。示例代码如下:
List<String> allIpList = NetUtil.getLocalIpAddress();List<String> ipv4List = new ArrayList<>();List<String> ipv6List = new ArrayList<>();for (String ip : allIpList) {if (NetUtil.isIpv4(ip)) {ipv4List.add(ip);} else if (NetUtil.isIpv6(ip)) {ipv6List.add(ip);}}System.out.println("IPv4 地址列表: " + ipv4List);System.out.println("IPv6 地址列表: " + ipv6List);
在这个例子中,我们首先使用
NetUtil.getLocalIpAddress()
方法获取所有的 IP 地址,遍历这个列表,使用
NetUtil.isIpv4()
和
NetUtil.isIpv6()
方法判断每个 IP 地址的类型,并将它们分别添加到 IPv4 和 IPv6 地址列表中。这样我们就可以分别获取 IPv4 和 IPv6 地址。
需要注意的是,对于某些特殊的 IP 地址,比如 loopback 地址 (127.0.0.1 和 ::1),Hutool 也将其识别为合法的 IP 地址。您只需要获取可用于访问的 IP 地址,可以在遍历 IP 地址列表时,额外判断一下地址是否为 loopback 地址。
除获取 IP 地址列表,Hutool 还提供一些其他的 IP 地址相关方法,比如:
这些方法都可以帮助我们更好地处理 IP 地址相关的需求。
Hutool 为我们提供非常便捷的 IP 地址获取和处理方法。使用这些方法,我们可以轻松地获取当前机器的所有 IP 地址,并且还能区分 IPv4 和 IPv6 地址。Hutool 还提供其他一些有用的 IP 地址相关方法,如判断 IP 地址是否为内网 IP、获取公网 IP 等。这些方法大大简化 IP 地址处理的开发工作,提高开发效率。
雪花算法,什么情况下发生ID冲突?
分布式系统中,有一些需要使用全局唯一ID的场景,这种时候为了防止ID冲突可以使用36位的UUID,但是UUID有一些缺点,首先他相对比较长,另外UUID一般是无序的。
有些时候我们希望能使用一种简单些的ID,并且希望ID能够按照时间有序生成。
Snowflake中文的意思是雪花,所以常被称为雪花算法,是Twitter开源的分布式ID生成算法。
Twitter雪花算法生成后是一个64bit的long型的数值,组成部分引入了时间戳,基本保持了自增。
SnowFlake算法的优点:
高性能高可用:生成时不依赖于数据库,完全在内存中生成;
高吞吐:每秒钟能生成数百万的自增ID;
ID自增:存入数据库中,索引效率高。
SnowFlake算法的缺点:
依赖与系统时间的一致性,如果系统时间被回调,或者改变,可能会造成ID冲突或者重复。
雪花算法组成snowflake结构如下图所示:
包含四个组成部分
不使用:1bit,最高位是符号位,0表示正,1表示负,固定为0。
时间戳:41bit,毫秒级的时间戳(41位的长度可以使用69年)。
标识位:5bit数据中心ID,5bit工作机器ID,两个标识位组合起来最多可以支持部署1024个节点。
序列号:12bit递增序列号,表示节点毫秒内生成重复,通过序列号表示唯一,12bit每毫秒可产生4096个ID。
通过序列号1毫秒可以产生4096个不重复ID,则1秒可以生成4096*1000=409wID。
默认的雪花算法是64bit,具体的长度可以自行配置。 如果希望运行更久,增加时间戳的位数;如果需要支持更多节点部署,增加标识位长度;如果并发很高,增加序列号位数。
总结:雪花算法并不是一成不变的,可以根据系统内具体场景进行定制。
雪花算法适用场景因为雪花算法有序自增,保障了MySQL中B+Tree索引结构插入高性能。
所以,日常业务使用中,雪花算法更多是被应用在数据库的主键ID和业务关联主键。
雪花算法生成ID重复问题假设:一个订单微服务,通过雪花算法生成ID,共部署三个节点,标识位一致。
此时有200并发,均匀散布三个节点,三个节点同一毫秒同一序列号下生成ID,那么就会产生重复ID。
通过上述假设场景,可以知道雪花算法生成ID冲突存在一定的前提条件:
服务通过集群的方式部署,其中部分机器标识位一致;
业务存在一定的并发量,没有并发量无法触发重复问题;
生成ID的时机:同一毫秒下的序列号一致。
标识位如何定义如果能保证标识位不重复,那么雪花ID也不会重复。
通过上面的案例,知道了ID重复的必要条件。 如果要避免服务内产生重复的ID,那么就需要从标识位上动文章。
我们先看看开源框架中使用雪花算法,如何定义标识位。
Mybatis-Plusv3.4.2雪花算法实现类Sequence,提供了两种构造方法:无参构造,自动生成dataCenterId和workerId;有参构造,创建Sequence时明确指定标识位。
Hutoolv5.7.9参照了Mybatis-PlusdataCenterId和workerId生成方案,提供了默认实现。
一起看下Sequence的创建默认无参构造,如何生成dataCenterId和workerId:
publicstaticlonggetDataCenterId(longmaxDatacenterId){longid=1L;finalbyte[]mac=();if(null!=mac){id=((0xFF&(long)mac[-2])|(0x0000FF00&(((long)mac[-1])<<8)))>>6;id=id%(maxDatacenterId+1);}returnid;}入参maxDatacenterId是一个固定值,代表数据中心ID最大值,默认值31。
为什么最大值要是31?因为5bit的二进制最大是,对应十进制数值31。
获取dataCenterId时存在两种情况,一种是网络接口为空,默认取1L;另一种不为空,通过Mac地址获取dataCenterId。
可以得知,dataCenterId的取值与Mac地址有关。
接下来再看看workerId:
publicstaticlonggetWorkerId(longdatacenterId,longmaxWorkerId){finalStringBuildermpid=newStringBuilder();(datacenterId);try{(());}catch(UtilExceptionigonre){//ignore}return(()()&0xffff)%(maxWorkerId+1);}入参maxWorkderId也是一个固定值,代表工作机器ID最大值,默认值31;datacenterId取自上述的getDatacenterId方法。
name变量值为PID@IP,所以name需要根据@分割并获取下标0,得到PID。
通过MAC+PID的hashcode获取16个低位,进行运算,最终得到workerId。
分配标识位Mybatis-Plus标识位的获取依赖Mac地址和进程PID,虽然能做到尽量不重复,但仍有小几率。
标识位如何定义才能不重复?有两种方案:预分配和动态分配。
预分配
应用上线前,统计当前服务的节点数,人工去申请标识位。
这种方案,没有代码开发量,在服务节点固定或者项目少可以使用,但是解决不了服务节点动态扩容性问题。
动态分配
通过将标识位存放在Redis、Zookeeper、MySQL等中间件,在服务启动的时候去请求标识位,请求后标识位更新为下一个可用的。
通过存放标识位,延伸出一个问题:雪花算法的ID是服务内唯一还是全局唯一。
以Redis举例,如果要做服务内唯一,存放标识位的Redis节点使用自己项目内的就可以;如果是全局唯一,所有使用雪花算法的应用,要用同一个Redis节点。
两者的区别仅是不同的服务间是否公用Redis。 如果没有全局唯一的需求,最好使ID服务内唯一,因为这样可以避免单点问题。
服务的节点数超过1024,则需要做额外的扩展;可以扩展10bit标识位,或者选择开源分布式ID框架。
动态分配实现方案
Redis存储一个Hash结构Key,包含两个键值对:dataCenterId和workerId。
在应用启动时,通过Lua脚本去Redis获取标识位。 dataCenterId和workerId的获取与自增在Lua脚本中完成,调用返回后就是可用的标示位。
具体Lua脚本逻辑如下:
第一个服务节点在获取时,Redis可能是没有snowflake_work_id_key这个Hash的,应该先判断Hash是否存在,不存在初始化Hash,dataCenterId、workerId初始化为0;
如果Hash已存在,判断dataCenterId、workerId是否等于最大值31,满足条件初始化dataCenterId、workerId设置为0返回;
dataCenterId和workerId的排列组合一共是1024,在进行分配时,先分配workerId;
判断workerId是否!=31,条件成立对workerId自增,并返回;如果workerId=31,自增dataCenterId并将workerId设置为0。
dataCenterId、workerId是一直向下推进的,总体形成一个环状。 通过Lua脚本的原子性,保证1024节点下的雪花算法生成不重复。 如果标识位等于1024,则从头开始继续循环推进。
Leaf和Uid都有实现雪花算法,Leaf额外提供了号段模式生成ID。
美团Leaf:网络Uid:雪花算法可以满足大部分场景,如无必要,不建议引入开源方案增加系统复杂度。
回顾总结文章通过图文并茂的方式帮助读者梳理了一遍什么是雪花算法,以及如何解决雪花算法生成ID冲突的问题。
关于雪环算法生成ID冲突问题,文中给了一种方案:分配标示位;通过分配雪花算法的组成标识位,来达到默认1024节点下ID生成唯一。
可以去看Hutool或者Mybatis-Plus雪花算法的具体实现,帮助大家更好的理解。
雪花算法不是万能的,并不能适用于所有场景。 如果ID要求全局唯一并且服务节点超出1024节点,可以选择修改算法本身的组成,即扩展标识位,或者选择开源方案:LEAF、UID。
创作不易,文章看完有帮助,点关注支持一下,祝好。