FastDFS布式文件系统仿抖音小视频实现

本文涉及的产品
云数据库 MongoDB,独享型 2核8GB
推荐场景:
构建全方位客户视图
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
简介: FastDFS布式文件系统仿抖音小视频实现

1、小视频功能说明

小视频功能类似于抖音、快手小视频的应用,用户可以上传小视频进行分享,也可以浏览查看别人分享的视频,并且可以对视频评论和点赞操作。

2、技术方案

对于小视频的功能的开发,核心点就是:存储 + 推荐 + 加载速度 。


对于存储而言,小视频的存储量以及容量都是非常巨大的

所以我们选择自己搭建分布式存储系统 FastDFS进行存储

对于推荐算法,我们将采用多种权重的计算方式进行计算

对于加载速度,除了提升服务器带宽外可以通过CDN的方式进行加速,当然了这需要额外购买CDN服务

3、FastDFS

3.1、FastDFS是什么?

FastDFS是分布式文件系统。使用 FastDFS很容易搭建一套高性能的文件服务器集群提供文件上传、下载等服务。


3.2、工作原理

FastDFS 架构包括 Tracker server 和 Storage server。客户端请求 Tracker server 进行文件上传、下载,通过 Tracker server 调度最终由 Storage server 完成文件上传和下载。


Tracker server 作用是负载均衡和调度,通过 Tracker server 在文件上传时可以根据一些策略找到 Storage server 提供文件上传服务。可以将 tracker 称为追踪服务器或调度服务器。


Storage server 作用是文件存储,客户端上传的文件最终存储在 Storage 服务器上,Storage server 没有实现自己的文件系统而是利用操作系统的文件系统来管理文件。可以将storage称为存储服务器。


每个 tracker 节点地位平等。收集 Storage 集群的状态。


Storage 分为多个组,每个组之间保存的文件是不同的。每个组内部可以有多个成员,组成员内部保存的内容是一样的,组成员的地位是一致的,没有主从的概念。


3.3、文件的上传

客户端上传文件后存储服务器将文件 ID 返回给客户端,此文件 ID 用于以后访问该文件的索引信息。文件索引信息包括:组名,虚拟磁盘路径,数据两级目录,文件名。


3.4、文件的下载

客户端下载请求到Tracker服务,Tracker返回给客户端storage的信息,客户端根据这些信息进行请求storage获取到文件。


4、FastDFS环境搭建

4.1、搭建服务

#拉取镜像
docker pull delron/fastdfs
#创建tracker容器
docker create --network=host --name tracker --restart=always -v fdfs-tracker:/var/fdfs delron/fastdfs tracker
#启动容器
docker start tracker
#创建storage容器
docker create --network=host --name storage --restart=always -e TRACKER_SERVER=192.168.111.131:22122 -v fdfs-storage:/var/fdfs -e GROUP_NAME=group1 delron/fastdfs storage
#启动容器
docker start storage
#进入storage容器,到storage的配置文件中配置http访问的端口,配置文件在/etc/fdfs目录下的storage.conf。
docker exec -it storage /bin/bash
#默认的http端口为8888,可以修改也可以配置
# the port of the web server on this storage server
http.server_port=8888
#配置nginx,在/usr/local/nginx目录下,修改nginx.conf文件
#默认配置如下:
    server {
        listen       8888;
        server_name  localhost;
        location ~/group[0-9]/ {
            ngx_fastdfs_module;
        }
        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root html;
        }
    }
#默认的存储路径为/var/fdfs/data

4.2、java client

导入依赖:

<dependency>
    <groupId>com.github.tobato</groupId>
    <artifactId>fastdfs-client</artifactId>
    <version>1.26.7</version>
    <exclusions>
        <exclusion>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
        </exclusion>
    </exclusions>
</dependency>

4.2.1、application.properties

# ===================================================================
# 分布式文件系统FDFS配置
# ===================================================================
fdfs.so-timeout = 1501
fdfs.connect-timeout = 601
#缩略图生成参数
fdfs.thumb-image.width= 150
fdfs.thumb-image.height= 150
#TrackerList参数,支持多个
fdfs.tracker-list=192.168.111.131:22122
#访问路径
fdfs.web-server-url=http://192.168.111.131:8888/

4.2.2、测试

package com.oldlu.server;
import com.github.tobato.fastdfs.domain.fdfs.StorePath;
import com.github.tobato.fastdfs.service.FastFileStorageClient;
import org.apache.commons.io.FileUtils;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import java.io.File;
import java.io.IOException;
@RunWith(SpringRunner.class)
@SpringBootTest
public class TestFastDFS {
    @Autowired
    protected FastFileStorageClient storageClient;
    @Test
    public void testUpload(){
        String path = "C:\\Users\\zhijun\\Desktop\\pics\\1.jpg";
        File file = new File(path);
        try {
            StorePath storePath = this.storageClient.uploadFile(FileUtils.openInputStream(file), file.length(), "jpg", null);
            System.out.println(storePath); //StorePath [group=group1, path=M00/00/00/wKgfUV2GJSuAOUd_AAHnjh7KpOc1.1.jpg]
            System.out.println(storePath.getFullPath());//group1/M00/00/00/wKgfUV2GJSuAOUd_AAHnjh7KpOc1.1.jpg
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

5、发布小视频

5.1、编写pojo

在dubbo接口工程中编写pojo:

package com.oldlu.dubbo.server.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.bson.types.ObjectId;
import org.springframework.data.mongodb.core.mapping.Document;
import java.util.List;
@Data
@NoArgsConstructor
@AllArgsConstructor
@Document(collection = "video")
public class Video implements java.io.Serializable {
    private static final long serialVersionUID = -3136732836884933873L;
    private ObjectId id; //主键id
    private Long userId;
    private String text; //文字
    private String picUrl; //视频封面文件
    private String videoUrl; //视频文件
    private Long created; //创建时间
    private Integer seeType; // 谁可以看,1-公开,2-私密,3-部分可见,4-不给谁看
    private List<Long> seeList; //部分可见的列表
    private List<Long> notSeeList; //不给谁看的列表
    private String longitude; //经度
    private String latitude; //纬度
    private String locationName; //位置名称
}

5.2、定义接口

package com.oldlu.dubbo.server.api;
import com.oldlu.dubbo.server.pojo.Video;
public interface VideoApi {
    /**
     * 保存小视频
     *
     * @param video
     * @return
     */
    Boolean saveVideo(Video video);
}

5.3、实现

package com.oldlu.dubbo.server.api;
import com.alibaba.dubbo.config.annotation.Service;
import com.oldlu.dubbo.server.pojo.Video;
import org.bson.types.ObjectId;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.mongodb.core.MongoTemplate;
@Service(version = "1.0.0")
public class VideoApiImpl implements VideoApi {
    @Autowired
    private MongoTemplate mongoTemplate;
    @Override
    public Boolean saveVideo(Video video) {
        if(video.getUserId() == null){
            return false;
        }
        video.setId(ObjectId.get());
        video.setCreated(System.currentTimeMillis());
        this.mongoTemplate.save(video);
        return true;
    }
}

5.4、接口服务

5.4.1、VideoController

package com.oldlu.server.controller;
import com.oldlu.server.service.VideoService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
@RestController
@RequestMapping("smallVideos")
public class VideoController {
    @Autowired
    private VideoService videoService;
    @PostMapping
    public ResponseEntity<Void> saveVideo(@RequestParam(value = "videoThumbnail", required = false) MultipartFile picFile,
                                          @RequestParam(value = "videoFile", required = false) MultipartFile videoFile) {
        try {
            Boolean bool = this.videoService.saveVideo(picFile, videoFile);
            if(bool){
                return ResponseEntity.ok(null);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
    }
}

5.4.2、VideoService

package com.oldlu.server.service;
import com.alibaba.dubbo.config.annotation.Reference;
import com.github.tobato.fastdfs.domain.conn.FdfsWebServer;
import com.github.tobato.fastdfs.domain.fdfs.StorePath;
import com.github.tobato.fastdfs.service.FastFileStorageClient;
import com.oldlu.dubbo.server.api.VideoApi;
import com.oldlu.dubbo.server.pojo.Video;
import com.oldlu.server.pojo.User;
import com.oldlu.server.utils.UserThreadLocal;
import com.oldlu.server.vo.PicUploadResult;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
@Service
public class VideoService {
    @Autowired
    private PicUploadService picUploadService;
    @Autowired
    protected FastFileStorageClient storageClient;
    @Autowired
    private FdfsWebServer fdfsWebServer;
    @Reference(version = "1.0.0")
    private VideoApi videoApi;
    public Boolean saveVideo(MultipartFile picFile, MultipartFile videoFile) {
        User user = UserThreadLocal.get();
        Video video = new Video();
        video.setUserId(user.getId());
        video.setSeeType(1);
        try {
            //上传封面图片
            PicUploadResult picUploadResult = this.picUploadService.upload(picFile);
            video.setPicUrl(picUploadResult.getName()); //图片路径
            //上传视频
            StorePath storePath = storageClient.uploadFile(videoFile.getInputStream(),
                    videoFile.getSize(),
                    StringUtils.substringAfter(videoFile.getOriginalFilename(), "."),
                    null);
            video.setVideoUrl(fdfsWebServer.getWebServerUrl() + "/" + storePath.getFullPath());
            this.videoApi.saveVideo(video);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return false;
    }
}

5.4.3、测试

如果上传视频,会导致异常,是因为请求太大的缘故:


解决:application.properties

spring.servlet.multipart.max-file-size=30MB
spring.servlet.multipart.max-request-size=30MB

6、小视频列表

6.1、定义dubbo服务

package com.oldlu.dubbo.server.api;
import com.oldlu.dubbo.server.pojo.Video;
import com.oldlu.dubbo.server.vo.PageInfo;
public interface VideoApi {
    /**
     * 保存小视频
     *
     * @param video
     * @return
     */
    Boolean saveVideo(Video video);
    /**
     * 分页查询小视频列表,按照时间倒序排序
     *
     * @param page
     * @param pageSize
     * @return
     */
    PageInfo<Video> queryVideoList(Integer page, Integer pageSize);
}

6.2、实现dubbo服务

package com.oldlu.dubbo.server.api;
import com.alibaba.dubbo.config.annotation.Service;
import com.oldlu.dubbo.server.pojo.Video;
import com.oldlu.dubbo.server.vo.PageInfo;
import org.bson.types.ObjectId;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Query;
import java.util.List;
@Service(version = "1.0.0")
public class VideoApiImpl implements VideoApi {
    @Autowired
    private MongoTemplate mongoTemplate;
    @Override
    public Boolean saveVideo(Video video) {
        if (video.getUserId() == null) {
            return false;
        }
        video.setId(ObjectId.get());
        video.setCreated(System.currentTimeMillis());
        this.mongoTemplate.save(video);
        return null;
    }
    @Override
    public PageInfo<Video> queryVideoList(Integer page, Integer pageSize) {
        Pageable pageable = PageRequest.of(page - 1, pageSize, Sort.by(Sort.Order.desc("created")));
        Query query = new Query().with(pageable);
        List<Video> videos = this.mongoTemplate.find(query, Video.class);
        PageInfo<Video> pageInfo = new PageInfo<>();
        pageInfo.setRecords(videos);
        pageInfo.setPageNum(page);
        pageInfo.setPageSize(pageSize);
        pageInfo.setTotal(0); //不提供总数
        return pageInfo;
    }
}

6.3、定义VideoVo

package com.oldlu.server.vo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class VideoVo {
    private String id;
    private Long userId;
    private String avatar; //头像
    private String nickname; //昵称
    private String cover; //封面
    private String videoUrl; //视频URL
    private String signature; //签名
    private Integer likeCount; //点赞数量
    private Integer hasLiked; //是否已赞(1是,0否)
    private Integer hasFocus; //是是否关注 (1是,0否)
    private Integer commentCount; //评论数量
}

6.4、VideoController

@RestController
@RequestMapping("smallVideos")
public class VideoController {    
/**
     * 查询小视频列表
     *
     * @param page
     * @param pageSize
     * @return
     */
    @GetMapping
    public ResponseEntity<PageResult> queryVideoList(@RequestParam(value = "page", defaultValue = "1") Integer page,
                                                     @RequestParam(value = "pagesize", defaultValue = "10") Integer pageSize) {
        try {
            if (page <= 0) {
                page = 1;
            }
            PageResult pageResult = this.videoService.queryVideoList(page, pageSize);
            if (null != pageResult) {
                return ResponseEntity.ok(pageResult);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
    }
}

6.5、VideoService

public PageResult queryVideoList(Integer page, Integer pageSize) {
        User user = UserThreadLocal.get();
        PageResult pageResult = new PageResult();
        pageResult.setPage(page);
        pageResult.setPagesize(pageSize);
        pageResult.setPages(0);
        pageResult.setCounts(0);
        PageInfo<Video> pageInfo = this.videoApi.queryVideoList(page, pageSize);
        List<Video> records = pageInfo.getRecords();
        List<VideoVo> videoVoList = new ArrayList<>();
        List<Long> userIds = new ArrayList<>();
        for (Video record : records) {
            VideoVo videoVo = new VideoVo();
            videoVo.setUserId(record.getUserId());
            videoVo.setCover(record.getPicUrl());
            videoVo.setVideoUrl(record.getVideoUrl());
            //因为ObjectId是16进制所以拿16进制的toHexString转
            videoVo.setId(record.getId().toHexString());
            videoVo.setSignature("我就是我~");
            Long commentCount = this.quanZiApi.queryCommentCount(videoVo.getId(), 2);
            videoVo.setCommentCount(commentCount == null ? 0 : commentCount.intValue()); // 评论数
            String followUserKey = "VIDEO_FOLLOW_USER_" + user.getId() + "_" + videoVo.getUserId();
            videoVo.setHasFocus(this.redisTemplate.hasKey(followUserKey) ? 1 : 0); //是否关注
            String userKey = "QUANZI_COMMENT_LIKE_USER_" + user.getId() + "_" + videoVo.getId();
            videoVo.setHasLiked(this.redisTemplate.hasKey(userKey) ? 1 : 0); //是否点赞(1是,0否)
            String key = "QUANZI_COMMENT_LIKE_" + videoVo.getId();
            String value = this.redisTemplate.opsForValue().get(key);
            if (StringUtils.isNotEmpty(value)) {
                videoVo.setLikeCount(Integer.valueOf(value)); //点赞数
            } else {
                videoVo.setLikeCount(0);
            }
            if (!userIds.contains(record.getUserId())) {
                userIds.add(record.getUserId());
            }
            videoVoList.add(videoVo);
        }
   if (userIds.size() > 0) {
        QueryWrapper<UserInfo> queryWrapper = new QueryWrapper();
        queryWrapper.in("user_id", userIds);
        List<UserInfo> userInfos = this.userInfoService.queryList(queryWrapper);
        for (VideoVo videoVo : videoVoList) {
            for (UserInfo userInfo : userInfos) {
                if (videoVo.getUserId().longValue() == userInfo.getUserId().longValue()) {
                    videoVo.setNickname(userInfo.getNickName());
                    videoVo.setAvatar(userInfo.getLogo());
                    break;
                }
            }
        }
  }
        pageResult.setItems(videoVoList);
        return pageResult;
    }

7、视频点赞

点赞逻辑与之前的圈子点赞一样,所以实现也是一样的。

    /**
     * 视频点赞
     *
     * @param videoId 视频id
     * @return
     */
    @PostMapping("/{id}/like")
    public ResponseEntity<Long> likeComment(@PathVariable("id") String videoId) {
        return this.movementsController.likeComment(videoId);
    }
    /**
     * 取消点赞
     *
     * @param videoId
     * @return
     */
    @PostMapping("/{id}/dislike")
    public ResponseEntity<Long> disLikeComment(@PathVariable("id") String videoId) {
        return this.movementsController.disLikeComment(videoId);
    }

8、小视频评论

小视频的评论与圈子的评论逻辑类似,所以也可以使用同一套逻辑进行实现。


8.1、评论列表

    /**
     * 评论列表
     */
    @GetMapping("/{id}/comments")
    public ResponseEntity<PageResult> queryCommentsList(@PathVariable("id") String videoId,
                                                        @RequestParam(value = "page", defaultValue = "1") Integer page,
                                                        @RequestParam(value = "pagesize", defaultValue = "10") Integer pagesize) {
        return this.commentsController.queryCommentsList(videoId, page, pagesize);
    }

8.2、发布评论

mock地址:https://mock.boxuegu.com/project/164/interface/api/72919

    /**
     * 提交评论
     *
     * @param param
     * @param videoId
     * @return
     */
    @PostMapping("/{id}/comments")
    public ResponseEntity<Void> saveComments(@RequestBody Map<String, String> param,
                                             @PathVariable("id") String videoId) {
        param.put("movementId", videoId);
        return this.commentsController.saveComments(param);
    }

8.3、评论点赞

  /**
     * 评论点赞
     *
     * @param publishId
     * @return
     */
    @PostMapping("/comments/{id}/like")
    public ResponseEntity<Long> commentsLikeComment(@PathVariable("id") String publishId) {
        return this.movementsController.likeComment(publishId);
    }
    /**
     * 评论取消点赞
     *
     * @param publishId
     * @return
     */
    @PostMapping("/comments/{id}/dislike")
    public ResponseEntity<Long> disCommentsLikeComment(@PathVariable("id") String publishId) {
        return this.movementsController.disLikeComment(publishId);
    }

8.4、关注用户

关注用户是关注小视频发布的作者,这样我们后面计算推荐时,关注的用户将权重更重一些。


8.4.1、定义dubbo服务

package com.oldlu.dubbo.server.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.bson.types.ObjectId;
import org.springframework.data.mongodb.core.mapping.Document;
@Data
@NoArgsConstructor
@AllArgsConstructor
@Document(collection = "follow_user")
public class FollowUser implements java.io.Serializable{
    private static final long serialVersionUID = 3148619072405056052L;
    private ObjectId id; //主键id
    private Long userId; //用户id
    private Long followUserId; //关注的用户id
    private Long created; //关注时间
}
package com.oldlu.dubbo.server.api;
import com.oldlu.dubbo.server.pojo.Video;
import com.oldlu.dubbo.server.vo.PageInfo;
public interface VideoApi {
    /**
     * 保存小视频
     *
     * @param video
     * @return
     */
    Boolean saveVideo(Video video);
    /**
     * 分页查询小视频列表,按照时间倒序排序
     *
     * @param page
     * @param pageSize
     * @return
     */
    PageInfo<Video> queryVideoList(Integer page, Integer pageSize);
    /**
     * 关注用户
     *
     * @param userId
     * @param followUserId
     * @return
     */
    Boolean followUser(Long userId, Long followUserId);
    /**
     * 取消关注用户
     *
     * @param userId
     * @param followUserId
     * @return
     */
    Boolean disFollowUser(Long userId, Long followUserId);
}

接口实现:

 //VideoApiImpl:   
 @Override
    public Boolean followUser(Long userId, Long followUserId) {
        try {
            FollowUser followUser = new FollowUser();
            followUser.setId(ObjectId.get());
            followUser.setUserId(userId);
            followUser.setFollowUserId(followUserId);
            followUser.setCreated(System.currentTimeMillis());
            this.mongoTemplate.save(followUser);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return false;
    }
    @Override
    public Boolean disFollowUser(Long userId, Long followUserId) {
        Query query = Query.query(Criteria.where("userId").is(userId).and("followUserId").is(followUserId));
        DeleteResult deleteResult = this.mongoTemplate.remove(query, FollowUser.class);
        return deleteResult.getDeletedCount() > 0;
    }

8.4.2、服务实现

//VideoController:
  /**
     * 视频用户关注
     */
    @PostMapping("/{id}/userFocus")
    public ResponseEntity<Void> saveUserFocusComments(@PathVariable("id") Long userId) {
        try {
            Boolean bool = this.videoService.followUser(userId);
            if (bool) {
                return ResponseEntity.ok(null);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
    }
    /**
     * 取消视频用户关注
     */
    @PostMapping("/{id}/userUnFocus")
    public ResponseEntity<Void> saveUserUnFocusComments(@PathVariable("id") Long userId) {
        try {
            Boolean bool = this.videoService.disFollowUser(userId);
            if (bool) {
                return ResponseEntity.ok(null);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
    }
//VideoService:
  /**
     * 关注用户
     *
     * @param userId
     * @return
     */
    public Boolean followUser(Long userId) {
        User user = UserThreadLocal.get();
        this.videoApi.followUser(user.getId(), userId);
        //记录已关注
        String followUserKey = "VIDEO_FOLLOW_USER_" + user.getId() + "_" + userId;
        this.redisTemplate.opsForValue().set(followUserKey, "1");
        return true;
    }
    /**
     * 取消关注
     *
     * @param userId
     * @return
     */
    public Boolean disFollowUser(Long userId) {
        User user = UserThreadLocal.get();
        this.videoApi.disFollowUser(user.getId(), userId);
        String followUserKey = "VIDEO_FOLLOW_USER_" + user.getId() + "_" + userId;
        this.redisTemplate.delete(followUserKey);
        return true;
    }
目录
相关文章
|
缓存 开发框架 监控
优化后端服务响应时间的关键策略与实践
在当今数字化时代,优化后端服务响应时间至关重要。本文探讨了几种关键策略和实践方法,帮助开发团队提高系统性能和用户体验。通过合理的资源分配、技术选型和代码优化,可以有效缩短响应时间,提升系统的整体效率和稳定性。【7月更文挑战第5天】
450 0
|
前端开发 Unix Linux
KVM 架构概述
【10月更文挑战第12天】KVM是基于硬件辅助虚拟化技术的虚拟机监控器,核心依赖于CPU的虚拟化支持如Intel VT和AMD-V。
|
JSON Go 数据库
Golang微服务框架居然可以开发单体应用?—— Kratos单体架构实践
微服务框架也是可以用于开发单体架构(monolith architecture)的应用。并且,单体应用也是最小的、最原始的、最初的项目状态,经过渐进式的开发演进,单体应用能够逐步的演变成微服务架构,并且不断的细分服务粒度。微服务框架开发的单体架构应用,既然是一个最小化的实施,那么它只需要使用到微服务框架最小的技术,也就意味着它只需要用到微服务框架最少的知识点,拿它来学习微服务框架是极佳的。
1399 0
|
人工智能 运维 Kubernetes
阿里云容器服务 ACK AI 助手正式上线
期待已久!阿里云容器服务 ACK AI 助手正式上线
|
网络协议 Docker 容器
Docker——如何修改运行中容器的映射端口
Docker——如何修改运行中容器的映射端口
608 0
|
安全 网络安全 数据安全/隐私保护
阿里云服务器不能发邮件禁用25端口的三种解决方法
阿里云服务器不能发邮件禁用25端口的三种解决方法
1789 0
|
数据安全/隐私保护 图形学
基于 LVGL 使用 SquareLine Studio 快速设计 UI 界面
基于 LVGL 使用 SquareLine Studio 快速设计 UI 界面
810 0
|
存储 负载均衡 调度
FastDFS安装手册
fastDFS架构包括 Tracker server和Storage server。客户端请求Tracker server进行文件上传、下载,通过Trackerserver调度最终由Storage server完成文件上传和下载。
4540 0
FastDFS安装手册
|
微服务
微服务项目之电商4.0技术架构图
微服务项目之电商4.0技术架构图
1385 0