跳至主要內容
谷粒商城分布式高级篇2

谷粒商城分布式高级篇2

检索服务

导入页面到search目录下面,编辑host文件加上最后一行

image-20240131193252288
image-20240131193252288

nginx修改:

    server_name  *.gulimall.com;

全民制作人ikun大约 13 分钟项目实战谷粒商城项目实战谷粒商城
谷粒商城分布式高级篇

谷粒商城分布式高级篇

ElasticSearch全文检索

基本概念

  1. 索引index

动词,相当于mysql的insert

名词,相当于mysql的databse

  1. 类型type

在index索引中,可以定义一个或者多个类型

类似于mysql的table,每一种类型的数据放在一起

  1. 文档Document

文档是json格式


全民制作人ikun大约 25 分钟项目实战谷粒商城项目实战谷粒商城
API开放平台

API开放平台

项目介绍

背景:

  1. 前端开发需要用到后台接口
  2. 使用现成的系统的功能

做一个提供API接口调用的平台,用户可以开通接口调用权限。用户可以使用接口,并且每次调用会进行统计。管理员可以发布接口、下线接口、接入接口,以及可视化接口的调用情况、数据。

  • 防止攻击(安全性)
  • 不能随便调用 (限制,开通)
  • 统计调用 次数
  • 计费
  • 流量保护
  • API接入

全民制作人ikun大约 22 分钟项目实战API开放平台项目实战API开放平台
黑马头条1

黑马头条1

登录逻辑

    @Override
    public ResponseResult login(LoginDto loginDto) {
        //nomal login need phone and password
        if (!StringUtils.isAnyBlank(loginDto.getPhone(), loginDto.getPassword())) {
            ApUser apUser = this.lambdaQuery()
                    .eq(ApUser::getPhone, loginDto.getPhone())
                    .one();
            if (apUser == null) {
                return ResponseResult.errorResult(AppHttpCodeEnum.DATA_NOT_EXIST, "用户不存在");
            }
            String salt = apUser.getSalt();
            String password = loginDto.getPassword();
            String encryptPassword = DigestUtils.md5DigestAsHex((password + salt).getBytes());
            if (!encryptPassword.equals(apUser.getPassword())) {
                return ResponseResult.errorResult(AppHttpCodeEnum.LOGIN_PASSWORD_ERROR);
            }
            //正确,返回jwt信息
            HashMap<String, Object> map = new HashMap<>();
            String token = AppJwtUtil.getToken(apUser.getId().longValue());
            map.put("token", token);
            apUser.setPassword("");
            apUser.setSalt("");
            map.put("user", apUser);
            return ResponseResult.okResult(map);
        }
        HashMap<String, Object> map = new HashMap<>();
        String token = AppJwtUtil.getToken(0L);
        map.put("token", token);
        return ResponseResult.okResult(map);
    }

全民制作人ikun大约 5 分钟项目实战黑马头条项目实战黑马头条
黑马点评6

黑马点评6

达人探店

发布探店笔记

tb_blog表 :

create table tb_blog
(
    id          bigint unsigned auto_increment comment '主键'
        primary key,
    shop_id     bigint                                   not null comment '商户id',
    user_id     bigint unsigned                          not null comment '用户id',
    title       varchar(255) collate utf8mb4_unicode_ci  not null comment '标题',
    images      varchar(2048)                            not null comment '探店的照片,最多9张,多张以","隔开',
    content     varchar(2048) collate utf8mb4_unicode_ci not null comment '探店的文字描述',
    liked       int unsigned default '0'                 null comment '点赞数量',
    comments    int unsigned                             null comment '评论数量',
    create_time timestamp    default CURRENT_TIMESTAMP   not null comment '创建时间',
    update_time timestamp    default CURRENT_TIMESTAMP   not null on update CURRENT_TIMESTAMP comment '更新时间'
)
    collate = utf8mb4_general_ci
    row_format = COMPACT;

全民制作人ikun大约 11 分钟项目实战黑马点评项目实战黑马点评
黑马点评7

黑马点评7

附近商户

GEO数据结构

GEO就是Geolocation的简写形式,代表地理坐标。Redis在3.2版本中加入了对GEO的支持,允许存储地理坐标信息,帮助我们根据经纬度来检索数据。常见的命令有:

  • GEOADD:添加一个地理空间信息,包含:经度(longitude)、纬度(latitude)、值(member)
  • GEODIST:计算指定的两个点之间的距离并返回
  • GEOHASH:将指定member的坐标转为hash字符串形式并返回
  • GEOPOS:返回指定member的坐标
  • GEORADIUS:指定圆心、半径,找到该圆内包含的所有member,并按照与圆心之间的距离排序后返回。6.以后已废弃
  • GEOSEARCH:在指定范围内搜索member,并按照与指定点之间的距离排序后返回。范围可以是圆形或矩形。6.2.新功能
  • GEOSEARCHSTORE:与GEOSEARCH功能一致,不过可以把结果存储到一个指定的key。 6.2.新功能

全民制作人ikun大约 6 分钟项目实战黑马点评项目实战黑马点评
黑马点评4

黑马点评4

分布式锁

分布式锁:满足分布式系统或集群模式下多进程可见并且互斥的锁。
分布式锁的核心思想就是让大家都使用同一把锁,只要大家使用的是同一把锁,那么我们就能锁住线程,不让线程进行,让程序串行执行,这就是分布式锁的核心思路
1653374296906.png
常见的分布式锁有三种
Mysql:mysql本身就带有锁机制,但是由于mysql性能本身一般,所以采用分布式锁的情况下,其实使用mysql作为分布式锁比较少
Redis:redis作为分布式锁是非常常见的一种使用方式,现在企业级开发中基本都使用redis或者zookeeper作为分布式锁,利用setnx这个方法,如果插入key成功,则表示获得到了锁,如果有人插入成功,其他人插入失败则表示无法获得到锁,利用这套逻辑来实现分布式锁
Zookeeper:zookeeper也是企业级开发中较好的一个实现分布式锁的方案。
1653382219377.png


全民制作人ikun大约 12 分钟项目实战黑马点评项目实战黑马点评
黑马点评5

黑马点评5

秒杀优化

用户模拟

模拟1000个用户同时发送请求,也就是需要使用jmeter进行压力测试,需要编写代码获取1000个用户的token

生成1000个登录用户并生成token


@SpringBootTest
class HmDianPingApplicationTests {

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    @Resource
    private IUserService userService;

    @Test
    @Transactional
    public void insertUser() {
        final String filePath = "src/main/resources/user.txt";
        final int count = 1000;
        BufferedWriter writer;
        try {
            writer = new BufferedWriter(new FileWriter(filePath));
            for (int i = 0; i < count; i++) {
                String phone = "13" + RandomUtil.randomNumbers(9);
                String token = this.login(phone);
                writer.write(token);
                writer.newLine();
            }
            writer.close();

        } catch (IOException e) {
            throw new RuntimeException(e);
        } finally {
            System.out.println("生成用户token完毕");
        }
    }

    public String login(String phone) {
        User user = new User();
        user.setPhone(phone);
        user.setNickName(USER_NICK_NAME_PREFIX + RandomUtil.randomString(10));
        userService.save(user);
        //生成token
        String token = UUID.randomUUID().toString();
        //将User对象转为HashMap存储
        UserDTO userDTO = BeanUtil.copyProperties(user, UserDTO.class);
        Map<String, Object> userMap = BeanUtil.beanToMap(userDTO, new HashMap<>(),
                CopyOptions.create()
                        .setIgnoreNullValue(true)
                        .setFieldValueEditor((fieldName, fieldvalue) -> fieldvalue.toString())
        );
        //存储
        String tokenKey = LOGIN_USER_KEY + token;
        stringRedisTemplate.opsForHash().putAll(tokenKey, userMap);
        //设置有效期
        stringRedisTemplate.expire(tokenKey, LOGIN_USER_TTL, TimeUnit.MINUTES);
        return token;
    }


}


全民制作人ikun大约 9 分钟项目实战黑马点评项目实战黑马点评
黑马点评2

黑马点评2

商品查询缓存

为什么要使用缓存?
缓存数据存储于代码中,而代码运行在内存中,内存的读写性能远高于磁盘,缓存可以大大降低用户访问并发量带来的服务器读写压力

添加商品缓存

查询数据库之前先查询缓存,如果缓存数据存在,则直接从缓存中返回,如果缓存数据不存在,再查询数据库,然后将数据存入redis。
1653322097736.png


全民制作人ikun大约 16 分钟项目实战黑马点评项目实战黑马点评
2
3