项目中Redis应用场景

2022-04-01  本文已影响0人  AC编程

一、Redis GEO-实现附近的人

1.1 Redis GEO介绍

Redis GEO主要用于存储地理位置信息,并对存储的信息进行操作,该功能在 Redis 3.2 版本新增。

Redis GEO操作方法有:

1.2 georadius与georadiusbymember区别

1、georadius以给定的经纬度为中心,返回键包含的位置元素当中,与中心的距离不超过给定最大距离的所有位置元素。

2、georadiusbymember和georadius命令一样,都可以找出位于指定范围内的元素,但是georadiusbymember的中心点是由给定的位置元素决定的,而不是使用经度和纬度来决定中心点。

georadius与georadiusbymember语法格式如下:

GEORADIUS key longitude latitude radius m|km|ft|mi [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count] [ASC|DESC] [STORE key] [STOREDIST key]

GEORADIUSBYMEMBER key member radius m|km|ft|mi [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count] [ASC|DESC] [STORE key] [STOREDIST key]

参数说明:

1.3 Java代码实现

1、初始化GeoOperations

GeoOperations<String, Object> geoOperations = redisTemplate.opsForGeo();

2、记录用户坐标

public boolean saveMemberCoordinate(String memberId,Double longitude,Double latitude) {
     Point point = new Point(longitude, latitude));
     Long result = geoOperations.add("member:coordinates", point, memberId);
     return result!=null ? result > 0 : Boolean.FALSE;
}

3、查用户附近的人

// distance:附近的距离,
// distanceUnit(距离单位):METERS=米(默认单位);KILOMETERS=公里;MILES=英里;FEET=英尺
public List<String> getNearbyMemberIds(String memberId,double distance,RedisGeoCommands.DistanceUnit distanceUnit){
    GeoResults<RedisGeoCommands.GeoLocation<Object>> content = geoOperations.geoRadiusByMember("member:coordinates", memberId, new Distance(distance, distanceUnit));

    List<String> memberIdList = new ArrayList<>(content.getContent().size());
    content.getContent().forEach(item -> {
        memberIdList.add(item.getContent().getName().toString());
    });
    return memberIdList;
}

4、GeoOperations源码

package org.springframework.data.redis.core;

public interface GeoOperations<K, M> {

  /**
     * Add {@link Point} with given member {@literal name} to {@literal key}.
     *
     * @param key must not be {@literal null}.
     * @param point must not be {@literal null}.
     * @param member must not be {@literal null}.
     * @return Number of elements added. {@literal null} when used in pipeline / transaction.
     * @since 2.0
     * @see <a href="https://redis.io/commands/geoadd">Redis Documentation: GEOADD</a>
     */
    @Nullable
    Long add(K key, Point point, M member);

  /**
     * Get the {@literal member}s within the circle defined by the {@literal members} coordinates and given
     * {@literal radius} applying {@link Metric}.
     *
     * @param key must not be {@literal null}.
     * @param member must not be {@literal null}.
     * @param distance must not be {@literal null}.
     * @return never {@literal null} unless used in pipeline / transaction.
     * @since 2.0
     * @see <a href="https://redis.io/commands/georadiusbymember">Redis Documentation: GEORADIUSBYMEMBER</a>
     */
    @Nullable
    GeoResults<GeoLocation<M>> radius(K key, M member, Distance distance);

    /**
     * Get the {@literal member}s within the circle defined by the {@literal members} coordinates and given
     * {@literal radius} applying {@link Metric}.
     *
     * @param key must not be {@literal null}.
     * @param member must not be {@literal null}.
     * @param distance must not be {@literal null}.
     * @return never {@literal null} unless used in pipeline / transaction.
     * @see <a href="https://redis.io/commands/georadiusbymember">Redis Documentation: GEORADIUSBYMEMBER</a>
     * @deprecated since 2.0, use {@link #radius(Object, Object, Distance)}.
     */
    @Deprecated
    @Nullable
    default GeoResults<GeoLocation<M>> geoRadiusByMember(K key, M member, Distance distance) {
        return radius(key, member, distance);
    }

}

二、zset、hash记录动态点赞

2.1 点赞操作记录

1、点赞/取消点赞参数VO

@Data
@ApiModel(value = "动态点赞VO")
public class DynamicLikeVO {

    /** 动态ID */
    @ApiModelProperty(value="动态ID",required = true)
    String dynamicId;

    /** 动态发布者 */
    @ApiModelProperty(value="动态发布者")
    Long dynamicOwnerId;

    @ApiModelProperty(value="是否点赞(true点赞 false 取消点赞)")
    Boolean isLike;
}

2、点赞数据存储

public Boolean setLikeOrRemoveDynamic(DynamicLikeVO likeVO, Long memberId) {
        List<String> key = new ArrayList<>();

        /**
         * 1、动态被点赞明细(记录点赞动态的用户id集合)
         * 2、key:{qm}dy:l:6130e1620fbe93767755bf41
         * 3、数据结构:zset
         * 4、数据样例:
         * [
         *   {
         *     id:1,
         *     member:157291823300609
         *     score:1631871153491
         *   },
         *   {
         *     id:1,
         *     member:148598543155201
         *     score:1632919179426
         *   }
         * ]
         */
        key.add(CacheKeyCombinationConstant.getDynamicLikeCacheKye(likeVO.getDynamicId()));

        /**
         * 1、动态被点赞总数(记录动态被点赞总数、以及动态其他汇总信息)
         * 2、{qm}dy:id:6204725d8fea23424d6b484f
         * 3、数据结构:hash
         * 4、数据样例:
         * {
         *    likeNums:3,
         *    readNums:10
         * }
         */
        key.add(CacheKeyCombinationConstant.getDynamicCacheKey(likeVO.getDynamicId()));

        /**
         * 1、用户(动态作者)被点赞总数(记录用户被点赞总数、以及其他汇总数据)
         * 2、{qm}mem:base:count:178158779432961
         * 3、数据结构:hash
         * 4、数据样例:
         * {
         *    likeNums:3,
         *    readNums:10
         * }
         */
        key.add(CacheKeyCombinationConstant.getMemberBaseCountCacheKey(likeVO.getDynamicOwnerId()));

        /**
         * 1、用户点赞明细(记录会员主动点赞过的动态id集合)
         * 2、{qm}mem:like:190337524301825
         * 3、数据结构:zset
         * 4、数据样例:
         * [
         *   {
         *     id:1,
         *     member:6130e2280fbe93767755ccb8
         *     score:1631871153491
         *   },
         *   {
         *     id:1,
         *     member:6130e4b10fbe93767755d2f8
         *     score:1632919179426
         *   }
         * ]
         */
        key.add(CacheKeyCombinationConstant.getMemberLikedCacheKey(memberId));

        /**
         * 1、用户(动态作者)被点赞明细(记录点赞用户id集合)
         * 2、{qm}mem:be:like:157484270551041
         * 3、数据结构:zset
         * 4、数据样例:
         * [
         *   {
         *     id:1,
         *     member:157291823300609
         *     score:1631871153491
         *   },
         *   {
         *     id:1,
         *     member:148598543155201
         *     score:1632919179426
         *   }
         * ]
         */
        key.add(CacheKeyCombinationConstant.getMemberBeLikedCacheKey(likeVO.getDynamicOwnerId()));

        // 需要用lua脚本保证事务
        Object o = this.redisScriptByResource(key, new ClassPathResource("lua/SetLikeOrUnlikeDynamic.lua"), System.currentTimeMillis(), likeVO.getDynamicId(), memberId, likeVO.getIsLike() ? 1 : 0);

        Boolean isLike = 0L == Long.parseLong(String.valueOf(o)) ? Boolean.FALSE : Boolean.TRUE;

        return isLike;
    }

3、SetLikeOrUnlikeDynamic.lua脚本

local dyLikeKey = KEYS[1]
local dyKey = KEYS[2]
local memberKey = KEYS[3]
local memberLikedKey = KEYS[4]
local memberBeLikedKey = KEYS[5]
local score = ARGV[1]
local dyId = ARGV[2]
local member = ARGV[3]
local isLike = tonumber(ARGV[4])

local data = redis.call("zscore", memberLikedKey, dyId)
local beLikedData = redis.call("zscore", memberBeLikedKey, member)

if (isLike ~= 0) then
    if(data) then --已经点赞
        return 0;
    else
        if(beLikedData) then --曾经给会员点过赞
            redis.call("zincrby", memberBeLikedKey, 1, member)
        else
            redis.call("zadd", memberBeLikedKey, 1, member) --作者获赞记录 谁给我点过赞
        end;
        redis.call("zadd",dyLikeKey, score,member) --动态点赞记录
        redis.call("zadd",memberLikedKey, score,dyId) --会员点赞记录
        redis.call("hincrby", dyKey, "likeNums", 1)  --动态获赞数
        redis.call("hincrby", memberKey, "likeNums", 1) --作者获赞数
        return 1
    end;
else
    if(data) then
        if(beLikedData) then
            redis.call("zincrby", memberBeLikedKey, -1, member)
        end;
        redis.call("zrem",dyLikeKey, member)
        redis.call("zrem",memberLikedKey, dyId)
        redis.call("hincrby", dyKey, "likeNums", -1)
        redis.call("hincrby", memberKey, "likeNums", -1)
        return 2
    else
        return 0
    end;
end;

2.2 点赞数据查询
2.2.1 判断用户是否给该动态点过赞
    /**
    * memberId:用户ID
    * dynamicId:动态ID  
    */
    public boolean userLikedDynamic(Long memberId,String dynamicId){
        /**
         * 1、用户点赞明细(记录会员主动点赞过的动态id集合)
         * 2、{qm}mem:like:190337524301825
         * 3、数据结构:zset
         * 4、数据样例:
         * [
         *   {
         *     id:1,
         *     member:6130e2280fbe93767755ccb8
         *     score:1631871153491
         *   },
         *   {
         *     id:1,
         *     member:6130e4b10fbe93767755d2f8
         *     score:1632919179426
         *   }
         * ]
         */
        Double likeNum = this.zScore(CacheKeyCombinationConstant.getMemberLikedCacheKey(memberId), dynamicId);

        if(likeNum!=null && likeNum>0){
            return true;
        }
        return false;
    }

    private ZSetOperations<String, Object> zSetOps;

    public Double zScore(String key, Object member) {
        return zSetOps.zScore(key, member);
    }
2.2.2 获取用户被点赞总数

    /**
     * 1、用户(动态作者)被点赞总数(记录用户被点赞总数、以及其他汇总数据)
     * 2、{qm}mem:base:count:178158779432961
     * 3、数据结构:hash
     * 4、数据样例:
     * {
     *    likeNums:3,
     *    readNums:10
     * }
     */
   public Long getMemberBeLikedCount(Long memberId) {
       Object likeNums = hashOps.get(CacheKeyCombinationConstant.getMemberBaseCountCacheKey(memberId), "likeNums");
       return null != likeNums ? Long.valueOf(String.valueOf(likeNums)) : 0L;
   }

三、list实现App首页动态列表

每个用户在App首页看到的动态列表数据都是不一样的,App首页动态列表数据由两部分组成:
1、根据用户的相关信息(年龄、性别、所在城市、兴趣标签等等)通过推荐算法推荐的动态列表数据。
2、公共的动态列表(用于兜底)

因此,Redis里有两个list集合:


    //默认推荐列表list
    String DYNAMIC_ANON_REC_DEFAULT = "rec:dy:mem:default";

    //给用户推荐的动态列表list
    String DYNAMIC_MEMBER_REC = "rec:dy:mem:用户id";

实现逻辑:
1、通过leftPop取用户推荐列表数据,取完的数据从用户推荐列表里弹出。
2、用户推荐列表里没数据了,去默认推荐列表里通过lrightPopLeftPush循环取数据,从右边弹出又从左边插入(达到循环效果,App首页动态永远也刷不完)。

核心代码

    //取用户推荐列表
    List<Object> recIds = redisUtil.lMultiPop(recKey, count);

    //取默认推荐列表
    List<Object> recIds = redisUtil.lrightPopLeftPush(recKey, recKey, count);

    /**
     * 右边弹出 左边插入
     * @param sourceKey
     * @param destinationKey
     * @param limit
     * @return
     */
    public List<Object> lrightPopLeftPush(String sourceKey, String destinationKey, int limit) {
        try {
            return redisTemplates.executePipelined(new SessionCallback<Object>() {
                @Override
                public <K, V> Map execute(RedisOperations<K, V> operations) throws DataAccessException {

                    ListOperations listOperations = operations.opsForList();
                    for (long i = 0; i < limit; i++) {
                        listOperations.rightPopAndLeftPush(sourceKey, destinationKey);
                    }
                    return null;
                }
            });
        } catch (Exception e) {
            e.printStackTrace();
            return new ArrayList<>();
        } finally {
            RedisConnectionUtils.unbindConnection(redisTemplates.getConnectionFactory());
        }
    }

    /**
     * 批量弹出
     * @param key
     * @param limit
     * @return
     */
    public List<Object> lMultiPop(String key, int limit) {
        try {
            return redisTemplates.executePipelined(new SessionCallback<Object>() {
                @Override
                public <K, V> Map execute(RedisOperations<K, V> operations) throws DataAccessException {

                    ListOperations listOperations = operations.opsForList();
                    for (long i = 0; i < limit; i++) {
                        listOperations.leftPop(key);
                    }
                    return null;
                }
            });
        } catch (Exception e) {
            e.printStackTrace();
            return new ArrayList<>();
        } finally {
            RedisConnectionUtils.unbindConnection(redisTemplates.getConnectionFactory());
        }
    }
上一篇 下一篇

猜你喜欢

热点阅读