国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

Redis GEO 類型與 API 結(jié)合,地理位置優(yōu)化的絕佳實(shí)踐

這篇具有很好參考價(jià)值的文章主要介紹了Redis GEO 類型與 API 結(jié)合,地理位置優(yōu)化的絕佳實(shí)踐。希望對(duì)大家有所幫助。如果存在錯(cuò)誤或未考慮完全的地方,請(qǐng)大家不吝賜教,您也可以點(diǎn)擊"舉報(bào)違法"按鈕提交疑問。

?? 嗨,您好 ?? 我是 vnjohn,在互聯(lián)網(wǎng)企業(yè)擔(dān)任 Java 開發(fā),CSDN 優(yōu)質(zhì)創(chuàng)作者
?? 推薦專欄:Spring、MySQL、Nacos、Java,后續(xù)其他專欄會(huì)持續(xù)優(yōu)化更新迭代
??文章所在專欄:MySQL、Redis、業(yè)務(wù)設(shè)計(jì)
?? 我當(dāng)前正在學(xué)習(xí)微服務(wù)領(lǐng)域、云原生領(lǐng)域、消息中間件等架構(gòu)、原理知識(shí)
?? 向我詢問任何您想要的東西,ID:vnjohn
??覺得博主文章寫的還 OK,能夠幫助到您的,感謝三連支持博客??
?? 代詞: vnjohn
? 有趣的事實(shí):音樂、跑步、電影、游戲

目錄

前言

在企業(yè)開發(fā)中,例如:附近服務(wù)門店/網(wǎng)點(diǎn)查詢、附近服務(wù)工人派單查詢,若沒有合理去設(shè)計(jì)地理位置的這塊查詢性能提升的功能時(shí),都是會(huì)去數(shù)據(jù)庫(kù)層面采用函數(shù)計(jì)算出來(lái),這種方式本來(lái)就存在一定的弊端

1、數(shù)據(jù)庫(kù)層面是性能瓶頸,將所有的壓力放在數(shù)據(jù)庫(kù)中,必然會(huì)給系統(tǒng)帶來(lái)災(zāi)難級(jí)的響應(yīng),例如:當(dāng)同時(shí)訪問的用戶量遞增時(shí),數(shù)據(jù)庫(kù)連接池打滿 > CPU 飄升 > 系統(tǒng)長(zhǎng)時(shí)間停留在數(shù)據(jù)庫(kù)層面無(wú)法及時(shí)響應(yīng)給用戶
2、當(dāng)服務(wù)門店/網(wǎng)點(diǎn)數(shù)據(jù)量越來(lái)越大時(shí)、服務(wù)工人數(shù)據(jù)越來(lái)越龐大時(shí),在使用函數(shù)計(jì)算篩選出附近的數(shù)據(jù),必然會(huì)造成數(shù)據(jù)庫(kù)的全表掃描 >explain type:ALL
3、當(dāng)最近的服務(wù)門店/網(wǎng)點(diǎn)、服務(wù)工人不滿足用戶的需求對(duì)象時(shí),會(huì)一直向下拉取下一頁(yè)的數(shù)據(jù),直至篩選到滿足自己的服務(wù)對(duì)象才停止,每一段的篩選都是一次性能極差的 SELECT

故而言之,因?yàn)檫@種問題的出現(xiàn),不得已而從其他方面去考慮來(lái)提升地理位置這塊的篩選動(dòng)作,由數(shù)據(jù)庫(kù)「磁盤存儲(chǔ)經(jīng)緯度」改為緩存「內(nèi)存存儲(chǔ)經(jīng)緯度」來(lái)提升重復(fù)的查詢操作

該文會(huì)演示從數(shù)據(jù)庫(kù)層面 > 緩存層面,地理位置的優(yōu)化提升改造

MySQL 數(shù)據(jù)庫(kù)

現(xiàn)大部分企業(yè)都采用 MySQL 作為數(shù)據(jù)庫(kù)存儲(chǔ),所以以 MySQL 8.0 為例,演練在它里面如何采用函數(shù)來(lái)完成地理位置的計(jì)算

表結(jié)構(gòu)

CREATE TABLE `shop` (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '門店id',
  `shop_no` varchar(64) NOT NULL COMMENT '門店編碼',
  `shop_name` varchar(50) NOT NULL COMMENT '門店名稱',
  `status` tinyint(1) NOT NULL DEFAULT '0' COMMENT '啟用狀態(tài):1-啟用、0-',
  `logo` varchar(255) DEFAULT NULL COMMENT '門店Logo',
  `introduce` text COMMENT '門店介紹',
  `longitude` double NOT NULL COMMENT '經(jīng)度',
  `latitude` double NOT NULL COMMENT '緯度',
  `trade_start_time` time DEFAULT NULL COMMENT '營(yíng)業(yè)開始時(shí)間',
  `trade_end_time` time DEFAULT NULL COMMENT '營(yíng)業(yè)結(jié)束時(shí)間',
  `contacts` varchar(20) DEFAULT NULL COMMENT '聯(lián)系人',
  `telephone` varchar(50) DEFAULT NULL COMMENT '商家聯(lián)系電話',
  `province_id` bigint DEFAULT NULL COMMENT '省id',
  `province` varchar(255) DEFAULT NULL COMMENT '省',
  `city_id` bigint DEFAULT NULL COMMENT '市id',
  `city` varchar(255) DEFAULT NULL COMMENT '市',
  `area_id` bigint DEFAULT NULL COMMENT '區(qū)id',
  `area` varchar(255) DEFAULT NULL COMMENT '區(qū)',
  `address` varchar(255) DEFAULT NULL COMMENT '門店詳細(xì)地址',
  `created_time` datetime DEFAULT NULL COMMENT '創(chuàng)建時(shí)間',
  `updated_time` datetime DEFAULT NULL COMMENT '更新時(shí)間',
  `is_deleted` tinyint(1) DEFAULT '0' COMMENT '是否刪除',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uni_shop_no` (`shop_no`) COMMENT '門店編碼唯一索引'
) ENGINE=InnoDB AUTO_INCREMENT=45 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='商家信息';

首先創(chuàng)建一張商家表「門店/服務(wù)網(wǎng)點(diǎn)」涉及到地理位置比較重要的兩個(gè)字段,longitude > 經(jīng)度、latitude > 緯度

經(jīng)度的最大值:180°
緯度的最大值:90°

模擬數(shù)據(jù)

使用存儲(chǔ)函數(shù),模擬生成十萬(wàn)條商家數(shù)據(jù)

CREATE DEFINER = `root` @`localhost` PROCEDURE `batchInsert` ( IN args INT ) BEGIN
	DECLARE
		-- 開啟事務(wù)
		i INT DEFAULT 1;
	START TRANSACTION;
	WHILE
			i <= args DO
			INSERT INTO shop ( shop_no, shop_name, `status`, longitude, latitude ) 
		VALUE
			(
				ROUND( RAND() * 99999 ),
				concat( "商家-", i ),1,
				-- 隨機(jī)生成經(jīng)緯度
				(RAND() * ( 179.077090052913654 - 0.477040512464626 )) + 0.477040512464626,
				(RAND() * ( 89.9172823750000134 - - 1.8840792500000134 )) + - 1.8840792500000134 
			);
		SET i = i + 1;
	END WHILE;
	COMMIT;
END
call batchInsert(100000);

數(shù)據(jù)庫(kù)查詢

不加索引

先使用「經(jīng)緯度」字段不加索引的方式執(zhí)行 SQL

EXPLAIN SELECT
	* 
FROM
	( SELECT id, ST_DISTANCE_SPHERE ( POINT ( 114.112808, 22.544977 ), POINT ( longitude, latitude )) AS distance FROM shop WHERE `STATUS` = 1 ) temp 
WHERE
	ROUND( distance / 1000, 2 ) BETWEEN 0 AND 20 
ORDER BY distance ASC 
LIMIT 5 

執(zhí)行計(jì)劃結(jié)果如下:

Redis GEO 類型與 API 結(jié)合,地理位置優(yōu)化的絕佳實(shí)踐,Redis,MySQL,業(yè)務(wù)設(shè)計(jì),redis,數(shù)據(jù)庫(kù),緩存

加索引

alter table shop add index `idx_location` (`longitude`,`latitude`) USING BTREE;

再次執(zhí)行 SQL,如下:

```sql
EXPLAIN SELECT
	* 
FROM
	( SELECT id, ST_DISTANCE_SPHERE ( POINT ( 114.112808, 22.544977 ), POINT ( longitude, latitude )) AS distance FROM shop WHERE `STATUS` = 1 ) temp 
WHERE
	ROUND( distance / 1000, 2 ) BETWEEN 0 AND 20 
ORDER BY distance ASC 
LIMIT 5 

執(zhí)行計(jì)劃結(jié)果如下:

Redis GEO 類型與 API 結(jié)合,地理位置優(yōu)化的絕佳實(shí)踐,Redis,MySQL,業(yè)務(wù)設(shè)計(jì),redis,數(shù)據(jù)庫(kù),緩存

直譯函數(shù)

MySQL 官方直譯 ST_DISTANCE_SPHERE 函數(shù)說明

語(yǔ)法:ST_Distance_Sphere(g1, g2 [, radius])
說明:

返回球體之間 Point 或 MultiPoint 參數(shù)之間的最小球面距離(以米為單位)可選 radius 參數(shù)應(yīng)以米為單位給出
如果兩個(gè)幾何參數(shù)都是有效的笛卡爾參數(shù) Point 或 MultiPoint SRID 0 中的值,則返回值是具有所提供半徑的球體上兩個(gè)幾何之間的最短距離。如果省略,則默認(rèn)半徑為 6,370,986 米,點(diǎn) X 和 Y 坐標(biāo)分別解釋為經(jīng)度和緯度(以度為單位)

如果任何參數(shù)的經(jīng)度或緯度超出范圍,則會(huì)發(fā)生錯(cuò)誤:

1、若經(jīng)度值不在 (?180, 180] 范圍內(nèi),則會(huì)發(fā)生 ER_GEOMETRY_PARAM_LONGITUDE_OUT_OF_RANGE 錯(cuò)誤(在 MySQL 8.0.12 ER_LONGITUDE_OUT_OF_RANGE 之前)
2、若緯度值不在 [?90, 90] 范圍內(nèi),則會(huì)發(fā)生 ER_GEOMETRY_PARAM_LATITUDE_OUT_OF_RANGE 錯(cuò)誤(在 MySQL 8.0.12 ER_LATITUDE_OUT_OF_RANGE 之前)

小結(jié)

從以上數(shù)據(jù)庫(kù)做地理位置篩選的結(jié)果來(lái)看,無(wú)論是否追加索引,似乎對(duì)數(shù)據(jù)庫(kù)的查詢性能來(lái)說,并沒有提升

使用數(shù)據(jù)庫(kù)做地理位置篩選,基于以下幾種情況可以考慮使用該方式進(jìn)行處理

商家「服務(wù)門店/網(wǎng)點(diǎn)」數(shù)據(jù)量不多
商家「服務(wù)門店/網(wǎng)點(diǎn)」模塊提供給用戶服務(wù)的入口較小

Redis 緩存

基于 Redis API 實(shí)現(xiàn)地理位置使用 GEO 有兩種方式

1、org.springframework.data.redis.core.RedisTemplate
2、org.redisson.api.RedissonClient

Redis GEO 客戶端

該篇節(jié),先告知大家如何應(yīng)用 Redis 客戶端的 GEO 類型,API 會(huì)基于客戶端的函數(shù)進(jìn)行一次封裝,先了解底層開始再到最后的高級(jí) API 實(shí)踐

1、查看 Redis 版本

redis-cli -v

2、連接 Redis 客戶端

1、redis-cli
2、無(wú)密碼直接登錄,有密碼通過:auth 明文密碼

3、查看 GEO、ZSet 幫助文檔

help @GEO
help @sorted-set

127.0.0.1:6379> help @GEO
  # GEO 指定的緩存 Key 追加 1~N 條經(jīng)緯度地理位置信息	
  GEOADD key [NX|XX] [CH] longitude latitude member [longitude latitude member ...]
  summary: Add one or more geospatial items in the geospatial index represented using a sorted set
  since: 3.2.0
  # GEO 指定的緩存 Key 兩個(gè)成員之間的距離
  # M|KM|FT|MI:米、公里、英里、英尺	
  GEODIST key member1 member2 [M|KM|FT|MI]
  summary: Returns the distance between two members of a geospatial index
  since: 3.2.0
  # GEO 指定緩存 Key 地理位置索引 > 標(biāo)準(zhǔn)地理散列字符串 
  GEOHASH key member [member ...]
  summary: Returns members of a geospatial index as standard geohash strings
  since: 3.2.0
  # GEO 指定緩存 Key 地理位置索引 > 成員對(duì)應(yīng)的經(jīng)緯度 
  GEOPOS key member [member ...]
  summary: Returns longitude and latitude of members of a geospatial index
  since: 3.2.0
  # GEO 指定緩存 Key:查詢表示地理空間索引的排序集,以傳入的經(jīng)緯度來(lái)獲取與點(diǎn)的給定最大距離匹配的成員,可按升序、降序排序
  GEORADIUS key longitude latitude radius M|KM|FT|MI [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count [ANY]] [ASC|DESC] [STORE key] [STOREDIST key]
  summary: Query a sorted set representing a geospatial index to fetch members matching a given maximum distance from a point
  since: 3.2.0
  # GEO 指定緩存 Key: 查詢表示地理空間索引的排序集,以傳入的指定成員經(jīng)緯度來(lái)獲取與點(diǎn)的給定最大距離匹配的成員,可按升序、降序排序
  GEORADIUSBYMEMBER key member radius M|KM|FT|MI [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count [ANY]] [ASC|DESC] [STORE key] [STOREDIST key]
  summary: Query a sorted set representing a geospatial index to fetch members matching a given maximum distance from a member
  since: 3.2.0
  # GEO 指定緩存 Key: 查詢表示地理空間索引的排序集,以傳入的指定成員經(jīng)緯度來(lái)獲取與點(diǎn)的給定最大距離匹配的成員,可按升序、降序排序,只支持可讀
  GEORADIUSBYMEMBER_RO key member radius M|KM|FT|MI [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count [ANY]] [ASC|DESC]
  summary: A read-only variant for GEORADIUSBYMEMBER
  since: 3.2.10
  # GEO 指定緩存 Key:查詢表示地理空間索引的排序集,以傳入的經(jīng)緯度來(lái)獲取與點(diǎn)的給定最大距離匹配的成員,可按升序、降序排序,只支持可讀
  GEORADIUS_RO key longitude latitude radius M|KM|FT|MI [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count [ANY]] [ASC|DESC]
  summary: A read-only variant for GEORADIUS
  since: 3.2.10
  # GEO 指定緩存 Key:查詢表示地理空間索引的排序集,以獲取「成員或指定經(jīng)緯度」最大距離匹配的成員,可按升序、降低排序,不支持存儲(chǔ)
  GEOSEARCH key FROMMEMBER member|FROMLONLAT longitude latitude BYRADIUS radius M|KM|FT|MI|BYBOX width height M|KM|FT|MI [ASC|DESC] [COUNT count [ANY]] [WITHCOORD] [WITHDIST] [WITHHASH]
  summary: Query a sorted set representing a geospatial index to fetch members inside an area of a box or a circle.
  since: 6.2.0
  # GEO 指定緩存 Key:查詢表示地理空間索引的排序集,以「獲取成員或指定經(jīng)緯度」最大距離匹配的成員,可按升序、降低排序,支持存儲(chǔ)至 ZSet Key 
  GEOSEARCHSTORE destination source FROMMEMBER member|FROMLONLAT longitude latitude BYRADIUS radius M|KM|FT|MI|BYBOX width height M|KM|FT|MI [ASC|DESC] [COUNT count [ANY]] [STOREDIST]
  summary: Query a sorted set representing a geospatial index to fetch members inside an area of a box or a circle, and store the result in another key.
  since: 6.2.0

引入 Spring、Redisson 配置

1、maven 依賴配置

<properties>
    <spring.boot.version>2.6.7</spring.boot.version>
    <redisson.version>3.17.5</redisson.version>
</properties>

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-dependencies</artifactId>
            <version>${spring.boot.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
        <dependency>
    		<groupId>org.redisson</groupId>
    		<artifactId>redisson-spring-boot-starter</artifactId>
    		<version>${redisson.version}</version>
		</dependency>
    </dependencies>
</dependencyManagement>

2、Redis 核心配置類,如下:

/**
 * Redis 核心配置類
 *
 * @author vnjohn
 * @since 2023
 */
@Configuration
public class RedisConfig {
    @Resource
    private RedisConnectionFactory factory;

    @Bean
    public RedisTemplate<String, Object> redisTemplate() {
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
        redisTemplate.setHashValueSerializer(new StringRedisSerializer());
        redisTemplate.setValueSerializer(new JdkSerializationRedisSerializer());
        redisTemplate.setStringSerializer(new StringRedisSerializer());
        redisTemplate.setDefaultSerializer(new StringRedisSerializer());
        redisTemplate.setConnectionFactory(factory);
        return redisTemplate;
    }
    
    @Bean
    public HashOperations<String, String, Object> hashOperations(RedisTemplate<String, Object> redisTemplate) {
        return redisTemplate.opsForHash();
    }

    @Bean
    public ValueOperations<String, String> valueOperations(RedisTemplate<String, String> redisTemplate) {
        return redisTemplate.opsForValue();
    }

    @Bean
    public GeoOperations<String, String> geoOperations(RedisTemplate<String, String> redisTemplate) {
        return redisTemplate.opsForGeo();
    }

    @Bean
    public ListOperations<String, Object> listOperations(RedisTemplate<String, Object> redisTemplate) {
        return redisTemplate.opsForList();
    }

    @Bean
    public SetOperations<String, Object> setOperations(RedisTemplate<String, Object> redisTemplate) {
        return redisTemplate.opsForSet();
    }

    @Bean
    public ZSetOperations<String, String> zSetOperations(RedisTemplate<String, String> redisTemplate) {
        return redisTemplate.opsForZSet();
    }
}

在本文,我們會(huì)用到 GeoOperations、ZSetOperations 操作類去調(diào)用 API

RedisTemplate API 操作

RedisTemplate 操作工具類,如下:

	@Resource
    private GeoOperations<String, String> geoOperations;
	
	@Resource
    private ZSetOperations<String, String> zSetOperations;

// ============================ sorted-set =============================

	public ZSetOperations.TypedTuple<String> redisTemplateZSetPopMinScore(String key) {
        return zSetOperations.popMin(key);
    }
	
// ============================ Geo =============================

    /**
     * 新增 Geo 某個(gè) Key 成員的經(jīng)緯度信息
     *
     * @param key       Redis 緩存 Key
     * @param longitude 經(jīng)度
     * @param latitude  緯度
     * @param member    成員
     */
    public void geoAdd(String key, Double longitude, Double latitude, String member) {
        Point point = new Point(longitude, latitude);
        geoOperations.add(key, point, member);
    }

    /**
     * 刪除 Geo 某個(gè) Key 成員的經(jīng)緯度信息
     *
     * @param key    Redis 緩存 Key
     * @param member 成員
     */
    public void geoRemove(String key, String member) {
        geoOperations.remove(key, member);
    }

    /**
     * 以半徑為單位,「千米」為計(jì)算單位展開,以倒序的方式展示對(duì)應(yīng)的信息
     *
     * @param key         緩存 Key
     * @param longitude   經(jīng)度
     * @param latitude    緯度
     * @param distanceNum 距離,單位:KM
     */
    public GeoResults<RedisGeoCommands.GeoLocation<String>> geoRadiusWithKilometers(String key, Double longitude,
                                                                                    Double latitude, Double distanceNum) {
        return geoRadiusWithKilometers(key, longitude, latitude, distanceNum, null, Boolean.TRUE);
    }

    /**
     * 以半徑為單位,「千米」為計(jì)算單位展開,以倒序的方式展示對(duì)應(yīng)的信息
     *
     * @param key         緩存 Key
     * @param longitude   經(jīng)度
     * @param latitude    緯度
     * @param limit       篩選條數(shù)
     * @param distanceNum 距離,單位:KM
     */
    public GeoResults<RedisGeoCommands.GeoLocation<String>> geoRadiusWithKilometersLimit(String key, Double longitude, Double latitude,
                                                                                         Integer limit, Double distanceNum) {
        return geoRadiusWithKilometers(key, longitude, latitude, distanceNum, limit, Boolean.TRUE);
    }

    /**
     * 以半徑為單位,「千米」為計(jì)算單位展開,以自定義順序方式展示對(duì)應(yīng)的信息
     *
     * @param key         緩存 Key
     * @param longitude   經(jīng)度
     * @param latitude    緯度
     * @param limit       篩選條數(shù)
     * @param distanceNum 距離,單位:KM
     * @param ascOrder    是否按升序排
     */
    public GeoResults<RedisGeoCommands.GeoLocation<String>> geoRadiusWithKilometers(String key, Double longitude, Double latitude,
                                                                                    Double distanceNum, Integer limit, Boolean ascOrder) {
        Point point = new Point(longitude, latitude);
        Distance radius = new Distance(distanceNum, Metrics.KILOMETERS);
        Circle within = new Circle(point, radius);
        RedisGeoCommands.GeoRadiusCommandArgs geoRadiusCommandArgs = RedisGeoCommands.GeoRadiusCommandArgs.newGeoRadiusArgs().includeCoordinates().includeDistance();
        if (null != limit) {
            geoRadiusCommandArgs = geoRadiusCommandArgs.limit(limit);
        }
        geoRadiusCommandArgs = ascOrder ? geoRadiusCommandArgs.sortAscending() : geoRadiusCommandArgs.sortDescending();
        return geoOperations.radius(key, within, geoRadiusCommandArgs);
    }

    /**
     * 以半徑為單位,「米」為計(jì)算單位展開,以倒序的方式展示對(duì)應(yīng)的信息
     *
     * @param key         緩存 Key
     * @param longitude   經(jīng)度
     * @param latitude    緯度
     * @param distanceNum 距離,單位:M
     */
    public GeoResults<RedisGeoCommands.GeoLocation<String>> geoRadiusWithMeters(String key, Double longitude,
                                                                                Double latitude, Double distanceNum) {
        return geoRadiusWithMeters(key, longitude, latitude, distanceNum, true);
    }

    /**
     * 以半徑為單位,「米」為計(jì)算單位展開,以自定義順序方式展示對(duì)應(yīng)的信息
     *
     * @param key         緩存 Key
     * @param longitude   經(jīng)度
     * @param latitude    緯度
     * @param distanceNum 距離,單位:M
     * @param ascOrder    是否按升序排
     */
    public GeoResults<RedisGeoCommands.GeoLocation<String>> geoRadiusWithMeters(String key, Double longitude,
                                                                                Double latitude, Double distanceNum,
                                                                                Boolean ascOrder) {
        Point point = new Point(longitude, latitude);
        Distance radius = new Distance(distanceNum, Metrics.NEUTRAL);
        Circle within = new Circle(point, radius);
        RedisGeoCommands.GeoRadiusCommandArgs geoRadiusCommandArgs = RedisGeoCommands.GeoRadiusCommandArgs.newGeoRadiusArgs().includeCoordinates().includeDistance();
        geoRadiusCommandArgs = ascOrder ? geoRadiusCommandArgs.sortAscending() : geoRadiusCommandArgs.sortDescending();
        return geoOperations.radius(key, within, geoRadiusCommandArgs);
    }

	public Long redisTemplateStoreSortedSearchTo(String destName, String key, Double longitude, Double latitude,
                                                 Double distanceNum, Integer limit, Boolean ascOrder) {

        Distance distance = new Distance(distanceNum, Metrics.KILOMETERS);
        RedisGeoCommands.GeoSearchStoreCommandArgs geoSearchStoreCommandArgs = RedisGeoCommands.GeoSearchStoreCommandArgs.newGeoSearchStoreArgs();
        geoSearchStoreCommandArgs.limit(limit);
        geoSearchStoreCommandArgs.sort(ascOrder ? Sort.Direction.ASC : Sort.Direction.DESC);
        GeoReference geoReference = GeoReference.fromCoordinate(longitude, latitude);
        Long searchAndStore = geoOperations.searchAndStore(key, destName, geoReference, distance, geoSearchStoreCommandArgs);
        return searchAndStore;
    }

1、geoAdd 方法 -> GEOADD 函數(shù)
2、geoRemove 方法 -> ZREM 函數(shù)

GEO 存儲(chǔ)起來(lái)以后放在 Redis 中是以 ZSet 結(jié)構(gòu)進(jìn)行存儲(chǔ)的,所以將 GEO 某個(gè)元素刪除時(shí),就調(diào)用 ZREM 函數(shù)進(jìn)行刪除即可

3、geoRadiusWithKilometers、geoRadiusWithMeters 方法操作的都是相同的函數(shù),只是篩選距離的單位不同,一個(gè)是千米、一個(gè)是米,它們對(duì)應(yīng)的函數(shù)有兩個(gè),GEORADIUS — 篩選附近距離的滿足元素、GEORADIUS_RO — 篩選附近距離的滿足元素,只支持可讀

具體的方法執(zhí)行邏輯可以查看以下方法源碼:RedisGeoCommands#GeoRadiusCommandArgs,該方法主要對(duì)我們傳入的參數(shù)進(jìn)行一次封裝,轉(zhuǎn)換為 Redis 中可識(shí)別的函數(shù)參數(shù)可選項(xiàng)

public GeoResults<GeoLocation<byte[]>> geoRadius(byte[] key, Circle within, GeoRadiusCommandArgs args) {
   List<Object> params = new ArrayList<Object>();
   params.add(key);
   params.add(convert(within.getCenter().getX()));
   params.add(convert(within.getCenter().getY()));
   params.add(within.getRadius().getValue());
   params.add(getAbbreviation(within.getRadius().getMetric()));
   
   RedisCommand<GeoResults<GeoLocation<byte[]>>> command;
   if (args.getFlags().contains(GeoRadiusCommandArgs.Flag.WITHCOORD)) {
       command = new RedisCommand<GeoResults<GeoLocation<byte[]>>>("GEORADIUS_RO", postitionDecoder);
       params.add("WITHCOORD");
   } else {
       MultiDecoder<GeoResults<GeoLocation<byte[]>>> distanceDecoder = new ListMultiDecoder2(new GeoResultsDecoder(within.getRadius().getMetric()), new GeoDistanceDecoder());
       command = new RedisCommand<GeoResults<GeoLocation<byte[]>>>("GEORADIUS_RO", distanceDecoder);
       params.add("WITHDIST");
   }
   
   if (args.getLimit() != null) {
       params.add("COUNT");
       params.add(args.getLimit());
   }
   if (args.getSortDirection() != null) {
       params.add(args.getSortDirection().name());
   }
   
   return read(key, ByteArrayCodec.INSTANCE, command, params.toArray());
}

引入 RedisTemplate API 有一些特性,我們?cè)趯?shí)際應(yīng)用中可能應(yīng)用不到,如:

1、當(dāng) GEO 中某個(gè)成員不知道它是否存在,當(dāng)不存在時(shí)可以直接新增,存在時(shí)不做任何變更,RedisTemplate API 需要操作兩次函數(shù):geoRemove、geoAdd,而下面要講解的 Redisson API 直接可以通過一個(gè)函數(shù)搞定,好處:減少一次與 Redis 之間的連接,提高操作效率
2、使用 Redisson 客戶端,實(shí)現(xiàn)「搜索滿足距離條件的成員列表」功能時(shí)更加的便捷

若 Redisson 版本不對(duì)時(shí),會(huì)在操作 redisTemplateZSetPopMinScore 方法時(shí),出現(xiàn)如下異常:

java.lang.StackOverflowError: null at org.springframework.data.redis.connection.DefaultedRedisConnection.zPopMin(DefaultedRedisConnection.java:973)
解決辦法:將 Redisson 版本降低到 3.15.6

Redisson API 操作

Redisson 操作工具類,如下:

private static final StringCodec REDISSON_CODE_C = new StringCodec();

@Resource
private RedissonClient redissonClient;

// ============================ ZSet Redisson =============================

public String redissonZSetPopMinScore(String key) {
    RScoredSortedSet<Object> scoredSortedSet = redissonClient.getScoredSortedSet(key, REDISSON_CODE_C);
    return (String) scoredSortedSet.pollFirst();
}

// ============================ Geo Redisson =============================

/**
 * 獲取 Redisson GEO 類型客戶端實(shí)例
 *
 * @param key 緩存 Key
 * @return 基于 Redisson GEO 操作的客戶端實(shí)例
 */
private RGeo<String> getRGeoClient(String key) {
    return redissonClient.getGeo(key, REDISSON_CODE_C);
}

/**
 * 若存在的話,替換 Geo 某個(gè) Key 成員的經(jīng)緯度信息
 */
public Boolean redissonGeoAddIfExists(String key, Object member, Double longitude, Double latitude) {
    RGeo<String> geo = getRGeoClient(key);
    return geo.addIfExists(new GeoEntry(longitude, latitude, member)) > 0;
}

/**
 * 刪除指定 Key > 多個(gè) Member 元素
 *
 * @param key     緩存 Key
 * @param members 成員列表
 */
public void redissonGeoRemove(String key, List<Long> members) {
    RGeo<String> geo = getRGeoClient(key);
    geo.removeAll(members);
}

/**
 * 新增 Geo 某個(gè) Key 成員的經(jīng)緯度信息
 *
 * @param key       緩存 Key
 * @param member    成員
 * @param longitude 經(jīng)度
 * @param latitude  緯度
 */
public void redissonGeoAdd(String key, Object member, Double longitude, Double latitude) {
    RGeo<String> geo = getRGeoClient(key);
    geo.add(new GeoEntry(longitude, latitude, member));
}

/**
 * 搜索滿足距離條件的成員列表
 *
 * @param key         緩存 Key
 * @param longitude   經(jīng)度
 * @param latitude    緯度
 * @param distanceNum 距離:KM
 * @return 匹配到的成員記錄及距離
 * @see GeoUnit geoUnit
 * 以半徑為中心距離,「geoUnit」為計(jì)算單位展開,以距離優(yōu)先展示對(duì)應(yīng)的信息
 */
public Map<String, Double> searchWithDistance(String key, Double longitude, Double latitude, Double distanceNum) {
    return searchWithDistance(key, longitude, latitude, distanceNum, GeoUnit.KILOMETERS, null);
}

/**
 * 搜索滿足距離條件的成員列表
 *
 * @param key         緩存 Key
 * @param longitude   經(jīng)度
 * @param latitude    緯度
 * @param distanceNum 距離:KM
 * @return 匹配到的成員記錄及距離
 * @see GeoUnit geoUnit
 * 以半徑為中心距離,「geoUnit」為計(jì)算單位展開,以距離優(yōu)先展示對(duì)應(yīng)的信息
 */
public Map<String, Double> searchWithDistance(String key, Double longitude, Double latitude, Double distanceNum, Integer limit) {
    return searchWithDistance(key, longitude, latitude, distanceNum, GeoUnit.KILOMETERS, limit);
}

/**
 * 搜索滿足距離條件的成員列表
 *
 * @param key         緩存 Key
 * @param longitude   經(jīng)度
 * @param latitude    緯度
 * @param distanceNum 距離
 * @param geoUnit     距離單位
 * @param limit       篩選條數(shù)
 * @return 匹配到的成員記錄及距離
 * @see GeoUnit geoUnit
 * 以半徑為中心距離,「geoUnit」為計(jì)算單位展開,以距離優(yōu)先展示對(duì)應(yīng)的信息
 */
public Map<String, Double> searchWithDistance(String key, Double longitude, Double latitude, Double distanceNum, GeoUnit geoUnit, Integer limit) {
    RGeo<String> geo = getRGeoClient(key);
    GeoSearchArgs args;
    if (null != limit) {
        args = GeoSearchArgs.from(longitude, latitude).radius(distanceNum, geoUnit).order(GeoOrder.ASC).count(limit);
    } else {
        args = GeoSearchArgs.from(longitude, latitude).radius(distanceNum, geoUnit).order(GeoOrder.ASC);
    }
    return geo.searchWithDistance(args);
}

/**
 * 存儲(chǔ)搜索滿足條件的成員列表
 *
 * @param destName    存儲(chǔ) ZSet Key
 * @param key         搜索目標(biāo) Key
 * @param longitude   經(jīng)度
 * @param latitude    緯度
 * @param distanceNum 距離
 */
public Boolean storeSortedSearchTo(String destName, String key, Double longitude, Double latitude,
                                   Double distanceNum) {
    return storeSortedSearchTo(destName, key, longitude, latitude, distanceNum, GeoUnit.KILOMETERS, null);
}

/**
 * 此處的應(yīng)用場(chǎng)景:
 * 1、當(dāng)用戶下單以后,通過該方法將用戶下單所在經(jīng)緯度最近的工人都統(tǒng)計(jì)出來(lái)
 * 2、統(tǒng)計(jì)出來(lái)的數(shù)據(jù)再次進(jìn)行一次比對(duì),若工人未開啟接單,那么該工人所在元素會(huì)被移除掉
 * 3、當(dāng)工人端拒絕接單,那么該工人所在元素從 ZSET 中移除
 * 4、當(dāng)工人端已接單并且開始服務(wù),那么該用戶所在的統(tǒng)計(jì)數(shù)據(jù)可被移除
 *
 * @param destName    存儲(chǔ) ZSet Key
 * @param key         搜索目標(biāo) Key
 * @param longitude   經(jīng)度
 * @param latitude    緯度
 * @param distanceNum 距離
 * @param geoUnit     距離單位
 * @param limit       條數(shù)
 */
public Boolean storeSortedSearchTo(String destName, String key, Double longitude, Double latitude,
                                   Double distanceNum, GeoUnit geoUnit, Integer limit) {
    RGeo<String> geo = getRGeoClient(key);
    GeoSearchArgs args;
    if (null != limit) {
        args = GeoSearchArgs.from(longitude, latitude)
                            .radius(distanceNum, geoUnit)
                            .order(GeoOrder.ASC)
                            .count(limit);
    } else {
        args = GeoSearchArgs.from(longitude, latitude)
                            .radius(distanceNum, geoUnit)
                            .order(GeoOrder.ASC);
    }
    return geo.storeSortedSearchTo(destName, args) > 0;
}

Redisson 中對(duì)不同的編碼還進(jìn)行了優(yōu)化,若知道當(dāng)前存儲(chǔ)或查詢的元素屬于非字符類型,可以通過以下類型來(lái)指定:

1、字符型:StringCodec,默認(rèn)使用 UTF-8 編碼方式
2、字節(jié)數(shù)組型:ByteArrayCodec
3、整型:IntegerCodec
4、浮點(diǎn)型:DoubleCodec

它們共同的父類為 BaseCodec,除了字符型,其他的編碼類型都有實(shí)現(xiàn)各自的解碼器

1、redissonGeoAdd 方法 -> GEOADD 函數(shù)
2、redissonGeoRemove 方法 -> ZREM 函數(shù)

與 RedisTemplate API 一致,GEO 存儲(chǔ)起來(lái)以后放在 Redis 中是以 ZSet 結(jié)構(gòu)進(jìn)行存儲(chǔ)的,所以將 GEO 某個(gè)元素刪除時(shí),就調(diào)用 ZREM 函數(shù)進(jìn)行刪除即可

3、redissonGeoAddIfExists -> GEOPOS、GEOADD 函數(shù)一起組合使用的

可觀察該方法的實(shí)現(xiàn):RedissonGeo#addIfExistsAsync,內(nèi)部使用 Redis Lua 腳本實(shí)現(xiàn)了這兩個(gè)函數(shù)的組合運(yùn)用,當(dāng) GEOPOS 返回的數(shù)據(jù)為真時(shí),那么就調(diào)用 GEOADD 函數(shù)將當(dāng)前元素存入 GEO Key 中

4、searchWithDistance 方法,它對(duì)應(yīng)的函數(shù)有兩個(gè),GEORADIUS — 篩選附近距離的滿足元素、GEORADIUS_RO — 篩選附近距離的滿足元素,只支持可讀
5、storeSortedSearchTo 方法,將篩選出來(lái)的內(nèi)容存儲(chǔ)到一個(gè)新的 ZSet Key 中

應(yīng)用場(chǎng)景如下:當(dāng)用戶在某個(gè)地點(diǎn)下單以后,需要篩選它附近可派單的工人,可篩選指定人數(shù)(只要滿足服務(wù)距離條件)存儲(chǔ)到新的 Key 中,當(dāng)存儲(chǔ)完成以后,即使第一個(gè)被派單的工人取消服務(wù)了,可以利用 ZSet 作為一個(gè)棧的結(jié)構(gòu),按照最近或最遠(yuǎn)的方式進(jìn)行一個(gè)一個(gè)的彈出來(lái) > Pop,結(jié)合 redissonZSetPopMinScore 方法天衣無(wú)縫!!

小結(jié)

1、若要使用 RedisTemplate API 中的 redisTemplateStoreSortedSearchTo 方法或者使用 Redisson API 中的 storeSortedSearchTo 方法,Redis 服務(wù)端的版本必須高于或等于 6.2.0

Redis GEO 類型與 API 結(jié)合,地理位置優(yōu)化的絕佳實(shí)踐,Redis,MySQL,業(yè)務(wù)設(shè)計(jì),redis,數(shù)據(jù)庫(kù),緩存

這兩個(gè)方法對(duì)應(yīng) Redis 中的 GEOSEARCHSTORE 函數(shù),可以使用 help GEOSEARCHSTORE 命令,結(jié)合幫助文檔運(yùn)用起來(lái)

2、在如何考慮是否引入一個(gè)新的組件,來(lái)減少對(duì)數(shù)據(jù)庫(kù)造成的壓力,就需要看地理位置這塊篩選的工作數(shù)據(jù)量有多大了,數(shù)據(jù)量大的話,寧愿基于內(nèi)存來(lái)完成地理位置篩選,也不要將查詢數(shù)據(jù)壓力放在基于磁盤的數(shù)據(jù)庫(kù)

3、引入一個(gè)新的組件,必然而然會(huì)考慮到引入這個(gè)組件會(huì)帶來(lái)哪些問題,那么又要解決好組件給我們的問題了,數(shù)據(jù)存儲(chǔ)到內(nèi)存中并不可靠,所以在對(duì)引入 Redis 組件時(shí),我們要把它的持久化機(jī)制考慮進(jìn)去,結(jié)合 Redis 保證地理位置查詢性能高效、持久化機(jī)制保證數(shù)據(jù)可靠

Redis 持久化機(jī)制類型:AOF、RDB
1、采用 AOF 方式進(jìn)行持久化,一行一行 Redis 命令會(huì)入文件,會(huì)導(dǎo)致文件過大,從而造成恢復(fù)數(shù)據(jù)速度會(huì)很慢,也會(huì)給機(jī)器磁盤帶來(lái)存儲(chǔ)壓力,好處就是能保證數(shù)據(jù)基本不丟失
2、采用 RDB 方式進(jìn)行持久化,會(huì)導(dǎo)致一部分?jǐn)?shù)據(jù)在瞬時(shí)丟失,從而就導(dǎo)致了數(shù)據(jù)存儲(chǔ)不可靠,好處就是恢復(fù)速率快
3、結(jié)合以上兩種方式都有缺點(diǎn),AOF+RDB 結(jié)合作為持久化方式,不僅僅用到了 AOF 數(shù)據(jù)可靠性也用到了 RDB 恢復(fù)數(shù)據(jù)的效率性

Redis 持久化機(jī)制 AOF、RDB、AOF+RDB 方式的詳細(xì)內(nèi)容,會(huì)在后續(xù)有文章進(jìn)行介紹,敬請(qǐng)期待?。?/p>

總結(jié)

該篇博文,主要先是進(jìn)行「地理位置」生產(chǎn)性能問題的全流程演化,從 MySQL -> +索引 -> 不 + 索引,使用了案例 SQL 進(jìn)行執(zhí)行計(jì)劃的分析,從而得出了 MySQL 在特殊場(chǎng)景下不適用于做地理位置的篩選工作「因?yàn)樗旧砘诖疟P的,在大數(shù)據(jù)量情況下,不能肆意打壓瓶頸」;隨即采用了 Redis GEO 類型來(lái)優(yōu)化了地理位置的篩選工作,結(jié)合 RedisTemplate、Redisson 客戶端 API 實(shí)戰(zhàn)函數(shù)進(jìn)行講解,從零到一教你如何運(yùn)用程序結(jié)合 Redis GEO 數(shù)據(jù)類型完成地理位置的優(yōu)化工程,希望此博文你能夠喜歡!

??????愿你我都能夠在寒冬中相互取暖,互相成長(zhǎng),只有不斷積累、沉淀自己,后面有機(jī)會(huì)自然能破冰而行!

博文放在 Redis 專欄里,歡迎訂閱,會(huì)持續(xù)更新!

如果覺得博文不錯(cuò),關(guān)注我 vnjohn,后續(xù)會(huì)有更多實(shí)戰(zhàn)、源碼、架構(gòu)干貨分享!

推薦專欄:Spring、MySQL,訂閱一波不再迷路

大家的「關(guān)注?? + 點(diǎn)贊?? + 收藏?」就是我創(chuàng)作的最大動(dòng)力!謝謝大家的支持,我們下文見!文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-714461.html

到了這里,關(guān)于Redis GEO 類型與 API 結(jié)合,地理位置優(yōu)化的絕佳實(shí)踐的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!

本文來(lái)自互聯(lián)網(wǎng)用戶投稿,該文觀點(diǎn)僅代表作者本人,不代表本站立場(chǎng)。本站僅提供信息存儲(chǔ)空間服務(wù),不擁有所有權(quán),不承擔(dān)相關(guān)法律責(zé)任。如若轉(zhuǎn)載,請(qǐng)注明出處: 如若內(nèi)容造成侵權(quán)/違法違規(guī)/事實(shí)不符,請(qǐng)點(diǎn)擊違法舉報(bào)進(jìn)行投訴反饋,一經(jīng)查實(shí),立即刪除!

領(lǐng)支付寶紅包贊助服務(wù)器費(fèi)用

相關(guān)文章

  • uniapp獲取地理位置的API是什么?

    UniApp獲取地理位置的API是 uni.getLocation 。它的作用是獲取用戶的當(dāng)前地理位置信息,包括經(jīng)緯度、速度、高度等。通過該API,開發(fā)者能夠?qū)崿F(xiàn)基于地理位置的功能,如顯示用戶所在位置附近的商家、導(dǎo)航服務(wù)、天氣查詢等。 以下是一個(gè)示例,展示如何使用uni.getLocation來(lái)獲取用

    2024年02月07日
    瀏覽(31)
  • 分享幾個(gè)IP獲取地理位置的API

    一、請(qǐng)求接口(GET): https://ip.taobao.com/outGetIpInfo?ip=IP地址accessKey=alibaba-inc 二、返回?cái)?shù)據(jù)格式: 三、頻次限制: 每個(gè)用戶的訪問頻率需小于1qps 四、文檔說明: http://ip.taobao.com/instructions.html 五、代碼片段: 一 、請(qǐng)求接口(GET): http://freeapi.ipip.net/ip地址字串 二、返回?cái)?shù)據(jù)

    2024年02月03日
    瀏覽(24)
  • 探索Redis特殊數(shù)據(jù)結(jié)構(gòu):Geospatial(地理位置)在實(shí)際中的應(yīng)用

    探索Redis特殊數(shù)據(jù)結(jié)構(gòu):Geospatial(地理位置)在實(shí)際中的應(yīng)用

    Redis官方提供了多種數(shù)據(jù)類型,除了常見的String、Hash、List、Set、zSet之外,還包括Stream、Geospatial、Bitmaps、Bitfields、Probabilistic(HyperLogLog、Bloom filter、Cuckoo filter、t-digest、Top-K、Count-min sketch、Configuration)和Time series。這些數(shù)據(jù)類型在Redis的數(shù)據(jù)結(jié)構(gòu)中發(fā)揮著各自獨(dú)特的作用。

    2024年02月20日
    瀏覽(29)
  • 用戶Ip地址和百度地圖api接口獲取用戶地理位置(經(jīng)緯度坐標(biāo),城市)

    ?php //獲取用戶ip(外網(wǎng)ip 服務(wù)器上可以獲取用戶外網(wǎng)Ip 本機(jī)ip地址只能獲取127.0.0.1) function ? getip(){ ???? if (! empty ( $_SERVER [ \\\"HTTP_CLIENT_IP\\\" ])){ ???? $cip ? =? $_SERVER [ \\\"HTTP_CLIENT_IP\\\" ]; ???? } ???? else ? if (! empty ( $_SERVER [ \\\"HTTP_X_FORWARDED_FOR\\\" ])){ ???? $cip ? =? $_SERVER [ \\\"HTTP_X_FOR

    2024年02月11日
    瀏覽(36)
  • 地理空間分析12——地理位置數(shù)據(jù)隱私與安全

    在數(shù)字化時(shí)代,地理位置數(shù)據(jù)成為了眾多應(yīng)用程序和服務(wù)不可或缺的一部分。從導(dǎo)航應(yīng)用到社交媒體,從廣告定位到城市規(guī)劃,地理位置數(shù)據(jù)的應(yīng)用范圍廣泛。然而,這些數(shù)據(jù)的收集和使用也引發(fā)了廣泛的隱私和安全擔(dān)憂。本文將探討地理位置數(shù)據(jù)隱私的挑戰(zhàn)和重要性,并介

    2024年03月19日
    瀏覽(28)
  • 小程序地理位置接口申請(qǐng)

    小程序地理位置接口申請(qǐng)

    申請(qǐng)接口理由: wx.chooseAddress 獲取用戶收貨地址提交理由: 快速定位用戶當(dāng)前位置,獲取省市區(qū)等地址信息,方便用戶提交地址等信息 wx.chooseLocation打開地圖選擇位置接口提交理由: 為了方便用戶能夠快速的獲取地址, 定位用戶位置信息 wx.getLocation獲取當(dāng)前地理位置,速度:

    2024年02月12日
    瀏覽(24)
  • 使用uniapp開發(fā)獲取地理位置

    使用uniapp開發(fā)獲取地理位置

    老板要求做一個(gè)微信小程序,后面又希望能轉(zhuǎn)為app. 所以選擇了uniapp開發(fā). 我的體驗(yàn)和感想就是以后不用uniapp了. 資源不多,學(xué)習(xí)了可能用處也不大.適合外包的干.這里寫一下使用uniapp開發(fā)微信小程序獲取地理位置 基本邏輯是使用uniapp的api首先獲得地理經(jīng)緯度位置等信息(在這之前

    2024年02月07日
    瀏覽(28)
  • Unity 獲取手機(jī)地理位置信息

    在游戲的開發(fā)過程中,有時(shí)候會(huì)遇到需要獲取玩家位置信息的需求,比如顯示玩家所在的國(guó)家城市等。 有一下方法可以參考: 可以根據(jù)手機(jī)的地區(qū)和語(yǔ)言來(lái)做判斷。 根據(jù)IP來(lái)判斷所處的位置,阿里云啥的都有對(duì)應(yīng)的接口服務(wù)。 根據(jù)GPS來(lái)判斷。 以上方法都各有利弊吧,這里簡(jiǎn)

    2024年02月12日
    瀏覽(19)
  • 通過ip獲取地理位置信息

    GeoLite2-City.mmdb 文件是 MaxMind 公司提供的一個(gè)免費(fèi)的 IP 地址與城市地理位置映射數(shù)據(jù)庫(kù)文件。它包含了 IP 地址范圍與對(duì)應(yīng)的城市、地區(qū)、國(guó)家、經(jīng)緯度等地理位置信息的映射。這種數(shù)據(jù)庫(kù)文件可以用于識(shí)別訪問您的應(yīng)用程序或網(wǎng)站的用戶的地理位置,從而實(shí)現(xiàn)針對(duì)不同地區(qū)的

    2024年02月12日
    瀏覽(20)
  • Unity之獲取用戶地理位置

    Unity之獲取用戶地理位置

    1.1 利用bilibili的api 【未知穩(wěn)定性】 lua代碼 ?1.2 利用baidu api 【配額超限,需要擴(kuò)充配額,需要聯(lián)系官方】 2.1?API: \\\"https://api.ipify.org\\\" 和 心知天氣官網(wǎng)“心知天氣 - 高精度氣象數(shù)據(jù) - 天氣數(shù)據(jù)API接口 - 行業(yè)氣象解決方案” 獲取公網(wǎng)IP 根據(jù)IP獲取地理信息和天氣信息,json反解析

    2024年02月14日
    瀏覽(19)

覺得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請(qǐng)作者喝杯咖啡吧~博客贊助

支付寶掃一掃領(lǐng)取紅包,優(yōu)惠每天領(lǐng)

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包