系统设计实战 173:173. 设计共享单车系统

4 阅读12分钟

🚀 系统设计实战 173:173. 设计共享单车系统

摘要:本文深入剖析系统的核心架构关键算法工程实践,提供完整的设计方案和面试要点。

你是否想过,设计共享单车系统进阶背后的技术挑战有多复杂?

系统概述

设计一个智能共享单车系统,支持车辆定位、智能开锁、动态计费、车辆调度和电子围栏管理,为用户提供便捷的短途出行服务。

核心功能需求

基础功能

  • 车辆定位和查找
  • 扫码开锁机制
  • 实时计费系统
  • 车辆状态监控
  • 电子围栏管理

高级功能

  • 智能车辆调度
  • 预测性维护
  • 动态定价策略
  • 碳足迹追踪
  • 用户行为分析

系统架构

整体架构

┌─────────────────┐    ┌─────────────────┐    ┌─────────────────┐
│   用户端应用     │    │   运营端系统     │    │   IoT 设备层    │
│                │    │                │    │                │
│ • 移动 App      │◄──►│ • 运营管理后台   │◄──►│ • 智能车锁      │
│ • 小程序        │    │ • 调度系统      │    │ • GPS 模块      │
│ • Web 端        │    │ • 维护系统      │    │ • 传感器       │
└─────────────────┘    └─────────────────┘    └─────────────────┘
                              │
                              ▼
┌─────────────────┐    ┌─────────────────┐    ┌─────────────────┐
│   API 网关      │    │   微服务集群     │    │   实时数据流     │
│                │    │                │    │                │
│ • 路由转发      │◄──►│ • 用户服务      │◄──►│ • Kafka 消息    │
│ • 认证鉴权      │    │ • 车辆服务      │    │ • Redis Stream  │
│ • 限流熔断      │    │ • 订单服务      │    │ • WebSocket     │
└─────────────────┘    │ • 支付服务      │    └─────────────────┘
                       │ • 地理服务      │
                       │ • 调度服务      │
                       └─────────────────┘
                              │
                              ▼
┌─────────────────┐    ┌─────────────────┐    ┌─────────────────┐
│   数据存储层     │    │   缓存层        │    │   外部服务      │
│                │    │                │    │                │
│ • PostgreSQL    │    │ • Redis 集群    │    │ • 地图 API      │
│ • MongoDB       │    │ • Memcached     │    │ • 支付网关      │
│ • InfluxDB      │    │ • 地理索引      │    │ • 短信服务      │
│ • Elasticsearch │    │                │    │ • 天气 API      │
└─────────────────┘    └─────────────────┘    └─────────────────┘

数据库设计

用户表 (users)

CREATE TABLE users (
    user_id BIGINT PRIMARY KEY,
    phone_number VARCHAR(20) UNIQUE NOT NULL,
    email VARCHAR(100),
    full_name VARCHAR(100),
    id_card_verified BOOLEAN DEFAULT FALSE,
    credit_score INT DEFAULT 100,
    account_balance DECIMAL(10,2) DEFAULT 0.00,
    total_rides INT DEFAULT 0,
    total_distance_km DECIMAL(10,2) DEFAULT 0.00,
    membership_level ENUM('basic', 'premium', 'vip') DEFAULT 'basic',
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);

单车表 (bikes)

CREATE TABLE bikes (
    bike_id VARCHAR(20) PRIMARY KEY,
    bike_model VARCHAR(50) NOT NULL,
    battery_level INT DEFAULT 100,
    current_lat DECIMAL(10,8),
    current_lng DECIMAL(11,8),
    current_address VARCHAR(500),
    bike_status ENUM('available', 'in_use', 'maintenance', 'offline', 'lost'),
    lock_status ENUM('locked', 'unlocked'),
    last_maintenance_date DATE,
    total_distance_km DECIMAL(10,2) DEFAULT 0.00,
    total_rides INT DEFAULT 0,
    manufacturing_date DATE,
    last_location_update TIMESTAMP,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);

订单表 (orders)

CREATE TABLE orders (
    order_id BIGINT PRIMARY KEY,
    user_id BIGINT NOT NULL,
    bike_id VARCHAR(20) NOT NULL,
    start_time DATETIME NOT NULL,
    end_time DATETIME,
    start_lat DECIMAL(10,8) NOT NULL,
    start_lng DECIMAL(11,8) NOT NULL,
    start_address VARCHAR(500),
    end_lat DECIMAL(10,8),
    end_lng DECIMAL(11,8),
    end_address VARCHAR(500),
    distance_km DECIMAL(8,3),
    duration_minutes INT,
    base_fee DECIMAL(6,2),
    time_fee DECIMAL(6,2),
    total_fee DECIMAL(8,2),
    order_status ENUM('active', 'completed', 'cancelled', 'abnormal'),
    payment_status ENUM('pending', 'paid', 'refunded'),
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    FOREIGN KEY (user_id) REFERENCES users(user_id),
    FOREIGN KEY (bike_id) REFERENCES bikes(bike_id)
);

电子围栏表 (geofences)

CREATE TABLE geofences (
    geofence_id INT PRIMARY KEY,
    fence_name VARCHAR(100) NOT NULL,
    fence_type ENUM('parking', 'no_parking', 'service', 'restricted'),
    coordinates JSON NOT NULL, -- 围栏坐标点数组
    city VARCHAR(50),
    district VARCHAR(50),
    is_active BOOLEAN DEFAULT TRUE,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

车辆轨迹表 (bike_tracks)

CREATE TABLE bike_tracks (
    track_id BIGINT PRIMARY KEY,
    bike_id VARCHAR(20) NOT NULL,
    order_id BIGINT,
    lat DECIMAL(10,8) NOT NULL,
    lng DECIMAL(11,8) NOT NULL,
    speed_kmh DECIMAL(5,2),
    battery_level INT,
    timestamp TIMESTAMP NOT NULL,
    FOREIGN KEY (bike_id) REFERENCES bikes(bike_id),
    FOREIGN KEY (order_id) REFERENCES orders(order_id)
);

核心服务设计

1. 车辆管理服务 (Bike Management Service)

// 时间复杂度:O(N),空间复杂度:O(1)

class BikeManagementService:
    def __init__(self):
        self.db = DatabaseConnection()
        self.redis = RedisCache()
        self.geo_service = GeospatialService()
        self.iot_service = IoTService()
    
    def update_bike_location(self, bike_id, lat, lng, battery_level, timestamp):
        """更新单车位置信息"""
        # 更新Redis中的实时位置
        location_data = {
            'bike_id': bike_id,
            'lat': lat,
            'lng': lng,
            'battery_level': battery_level,
            'timestamp': timestamp.isoformat(),
            'last_update': datetime.now().isoformat()
        }
        
        # 存储到Redis地理索引
        self.redis.geoadd('bike_locations', lng, lat, bike_id)
        self.redis.set(f"bike_status:{bike_id}", json.dumps(location_data), ex=300)
        
        # 异步更新数据库
        self.update_bike_location_async(bike_id, lat, lng, battery_level, timestamp)
        
        # 检查电子围栏
        self.check_geofence_violations(bike_id, lat, lng)
        
        # 检查电量预警
        if battery_level < 20:
            self.trigger_low_battery_alert(bike_id, lat, lng, battery_level)
    
    def find_nearby_bikes(self, user_lat, user_lng, radius_km=1.0, limit=20):
        """查找附近可用单车"""
        # 使用Redis地理索引快速查找
        nearby_bikes = self.redis.georadius(
            'bike_locations', 
            user_lng, user_lat, 
            radius_km, 
            unit='km',
            withdist=True,
            withcoord=True,
            count=limit
        )
        
        available_bikes = []
        for bike_data in nearby_bikes:
            bike_id = bike_data[0].decode('utf-8')
            distance = bike_data[1]
            coordinates = bike_data[2]
            
            # 获取单车详细状态
            bike_status = self.get_bike_status(bike_id)
            
            if bike_status and bike_status['bike_status'] == 'available':
                available_bikes.append({
                    'bike_id': bike_id,
                    'distance_meters': int(distance * 1000),
                    'lat': coordinates[1],
                    'lng': coordinates[0],
                    'battery_level': bike_status['battery_level'],
                    'bike_model': bike_status.get('bike_model', 'standard')
                })
        
        return available_bikes
    
    def unlock_bike(self, bike_id, user_id, unlock_lat, unlock_lng):
        """解锁单车"""
        # 验证用户权限
        if not self.validate_user_unlock_permission(user_id):
            raise PermissionError("用户无解锁权限")
        
        # 检查单车状态
        bike_status = self.get_bike_status(bike_id)
        if bike_status['bike_status'] != 'available':
            raise ValueError("单车不可用")
        
        # 检查距离(防止远程解锁)
        bike_location = (bike_status['lat'], bike_status['lng'])
        user_location = (unlock_lat, unlock_lng)
        distance = self.geo_service.calculate_distance(bike_location, user_location)
        
        if distance > 100:  # 100米范围内才能解锁
            raise ValueError("距离单车太远,无法解锁")
        
        # 发送解锁指令到IoT设备
        unlock_result = self.iot_service.send_unlock_command(bike_id)
        
        if unlock_result['success']:
            # 更新单车状态
            self.db.update('bikes', {'bike_id': bike_id}, {
                'bike_status': 'in_use',
                'lock_status': 'unlocked',
                'updated_at': datetime.now()
            })
            
            # 创建订单
            order_id = self.create_ride_order(user_id, bike_id, unlock_lat, unlock_lng)
            
            # 清除可用单车缓存
            self.redis.zrem('bike_locations', bike_id)
            
            return {
                'success': True,
                'order_id': order_id,
                'unlock_code': unlock_result['unlock_code']
            }
        else:
            raise Exception("解锁失败,请重试")
    
    def lock_bike(self, bike_id, user_id, lock_lat, lock_lng):
        """锁定单车"""
        # 获取当前订单
        current_order = self.db.query_one("""
            SELECT * FROM orders 
            WHERE bike_id = %s AND user_id = %s 
            AND order_status = 'active'
        """, [bike_id, user_id])
        
        if not current_order:
            raise ValueError("未找到有效订单")
        
        # 检查停车区域
        parking_validation = self.validate_parking_location(lock_lat, lock_lng)
        
        # 发送锁定指令
        lock_result = self.iot_service.send_lock_command(bike_id)
        
        if lock_result['success']:
            # 计算费用
            ride_fee = self.calculate_ride_fee(current_order, lock_lat, lock_lng)
            
            # 更新订单
            self.complete_ride_order(
                current_order['order_id'], 
                lock_lat, lock_lng, 
                ride_fee,
                parking_validation
            )
            
            # 更新单车状态
            self.db.update('bikes', {'bike_id': bike_id}, {
                'bike_status': 'available',
                'lock_status': 'locked',
                'current_lat': lock_lat,
                'current_lng': lock_lng,
                'updated_at': datetime.now()
            })
            
            # 重新加入可用单车索引
            self.redis.geoadd('bike_locations', lock_lng, lock_lat, bike_id)
            
            return {
                'success': True,
                'total_fee': ride_fee['total_fee'],
                'parking_fee': ride_fee.get('parking_fee', 0)
            }
        else:
            raise Exception("锁定失败,请重试")

2. 地理围栏服务 (Geofence Service)

class GeofenceService:
    def __init__(self):
        self.db = DatabaseConnection()
        self.redis = RedisCache()
    
    def check_geofence_violations(self, bike_id, lat, lng):
        """检查地理围栏违规"""
        point = (lat, lng)
        
        # 获取该区域的围栏
        nearby_fences = self.get_nearby_geofences(lat, lng)
        
        violations = []
        for fence in nearby_fences:
            if self.point_in_polygon(point, fence['coordinates']):
                if fence['fence_type'] == 'no_parking':
                    violations.append({
                        'type': 'illegal_parking',
                        'fence_id': fence['geofence_id'],
                        'fence_name': fence['fence_name']
                    })
                elif fence['fence_type'] == 'restricted':
                    violations.append({
                        'type': 'restricted_area',
                        'fence_id': fence['geofence_id'],
                        'fence_name': fence['fence_name']
                    })
        
        if violations:
            self.handle_geofence_violations(bike_id, lat, lng, violations)
        
        return violations
    
    def validate_parking_location(self, lat, lng):
        """验证停车位置"""
        point = (lat, lng)
        nearby_fences = self.get_nearby_geofences(lat, lng)
        
        parking_info = {
            'is_valid': True,
            'parking_fee': 0,
            'warnings': []
        }
        
        for fence in nearby_fences:
            if self.point_in_polygon(point, fence['coordinates']):
                if fence['fence_type'] == 'no_parking':
                    parking_info['is_valid'] = False
                    parking_info['parking_fee'] += 10  # 违规停车费
                    parking_info['warnings'].append('禁停区域,将收取违规停车费')
                elif fence['fence_type'] == 'parking':
                    parking_info['warnings'].append('推荐停车区域')
        
        return parking_info
    
    def point_in_polygon(self, point, polygon_coords):
        """判断点是否在多边形内(射线法)"""
        x, y = point
        n = len(polygon_coords)
        inside = False
        
        p1x, p1y = polygon_coords[0]
        for i in range(1, n + 1):
            p2x, p2y = polygon_coords[i % n]
            if y > min(p1y, p2y):
                if y <= max(p1y, p2y):
                    if x <= max(p1x, p2x):
                        if p1y != p2y:
                            xinters = (y - p1y) * (p2x - p1x) / (p2y - p1y) + p1x
                        if p1x == p2x or x <= xinters:
                            inside = not inside
            p1x, p1y = p2x, p2y
        
        return inside

3. 智能调度服务 (Smart Dispatch Service)

class SmartDispatchService:
    def __init__(self):
        self.db = DatabaseConnection()
        self.ml_model = DemandPredictionModel()
        self.optimization_engine = OptimizationEngine()
    
    def predict_demand_hotspots(self, city, time_window):
        """预测需求热点"""
        # 获取历史数据
        historical_data = self.db.query("""
            SELECT 
                ROUND(start_lat, 4) as lat_grid,
                ROUND(start_lng, 4) as lng_grid,
                COUNT(*) as ride_count,
                AVG(duration_minutes) as avg_duration,
                HOUR(start_time) as hour_of_day,
                DAYOFWEEK(start_time) as day_of_week
            FROM orders 
            WHERE start_time >= DATE_SUB(NOW(), INTERVAL 30 DAY)
            AND start_address LIKE %s
            GROUP BY lat_grid, lng_grid, hour_of_day, day_of_week
        """, [f"%{city}%"])
        
        # 使用机器学习模型预测
        features = self.prepare_prediction_features(historical_data, time_window)
        demand_predictions = self.ml_model.predict_demand(features)
        
        # 识别高需求区域
        hotspots = []
        for prediction in demand_predictions:
            if prediction['predicted_demand'] > prediction['current_supply']:
                hotspots.append({
                    'lat': prediction['lat'],
                    'lng': prediction['lng'],
                    'demand_gap': prediction['predicted_demand'] - prediction['current_supply'],
                    'priority': self.calculate_dispatch_priority(prediction)
                })
        
        return sorted(hotspots, key=lambda x: x['priority'], reverse=True)
    
    def generate_dispatch_tasks(self):
        """生成调度任务"""
        # 获取需求热点
        hotspots = self.predict_demand_hotspots('all', 60)  # 未来1小时
        
        # 获取可调度车辆
        available_bikes = self.get_redistributable_bikes()
        
        # 优化调度方案
        dispatch_plan = self.optimization_engine.optimize_bike_distribution(
            hotspots, available_bikes
        )
        
        # 生成调度任务
        dispatch_tasks = []
        for plan in dispatch_plan:
            task = {
                'task_id': self.generate_task_id(),
                'bike_ids': plan['bike_ids'],
                'source_location': plan['source'],
                'target_location': plan['target'],
                'priority': plan['priority'],
                'estimated_bikes_needed': plan['bike_count'],
                'task_type': 'rebalancing',
                'created_at': datetime.now()
            }
            dispatch_tasks.append(task)
        
        # 保存调度任务
        for task in dispatch_tasks:
            self.db.insert('dispatch_tasks', task)
        
        return dispatch_tasks
    
    def get_redistributable_bikes(self):
        """获取可重新分配的单车"""
        # 查找低需求区域的单车
        low_demand_bikes = self.db.query("""
            SELECT b.bike_id, b.current_lat, b.current_lng,
                   COUNT(o.order_id) as recent_rides
            FROM bikes b
            LEFT JOIN orders o ON b.bike_id = o.bike_id 
                AND o.start_time >= DATE_SUB(NOW(), INTERVAL 2 HOUR)
            WHERE b.bike_status = 'available'
            AND b.battery_level > 50
            GROUP BY b.bike_id, b.current_lat, b.current_lng
            HAVING recent_rides = 0
        """)
        
        return low_demand_bikes

4. 计费服务 (Billing Service)

class BillingService:
    def __init__(self):
        self.db = DatabaseConnection()
        self.pricing_engine = DynamicPricingEngine()
    
    def calculate_ride_fee(self, order, end_lat, end_lng):
        """计算骑行费用"""
        start_time = order['start_time']
        end_time = datetime.now()
        duration_minutes = (end_time - start_time).total_seconds() / 60
        
        # 计算距离
        start_point = (order['start_lat'], order['start_lng'])
        end_point = (end_lat, end_lng)
        distance_km = self.calculate_distance(start_point, end_point)
        
        # 基础费用结构
        base_fee = 1.0  # 起步费
        time_rate = 0.5  # 每分钟费率
        
        # 动态定价调整
        pricing_factors = {
            'time_of_day': datetime.now().hour,
            'day_of_week': datetime.now().weekday(),
            'weather': self.get_weather_condition(),
            'demand_level': self.get_current_demand_level(order['start_lat'], order['start_lng'])
        }
        
        dynamic_multiplier = self.pricing_engine.calculate_multiplier(pricing_factors)
        
        # 计算费用
        time_fee = duration_minutes * time_rate * dynamic_multiplier
        total_fee = base_fee + time_fee
        
        # 检查停车费用
        parking_validation = self.validate_parking_location(end_lat, end_lng)
        parking_fee = parking_validation.get('parking_fee', 0)
        
        return {
            'base_fee': base_fee,
            'time_fee': time_fee,
            'parking_fee': parking_fee,
            'total_fee': total_fee + parking_fee,
            'duration_minutes': int(duration_minutes),
            'distance_km': round(distance_km, 2),
            'dynamic_multiplier': dynamic_multiplier
        }
    
    def process_payment(self, order_id, fee_breakdown):
        """处理支付"""
        order = self.db.get_order(order_id)
        user = self.db.get_user(order['user_id'])
        
        total_amount = fee_breakdown['total_fee']
        
        # 检查账户余额
        if user['account_balance'] >= total_amount:
            # 余额支付
            self.deduct_balance(user['user_id'], total_amount)
            payment_method = 'balance'
        else:
            # 调用第三方支付
            payment_result = self.process_external_payment(
                user['user_id'], total_amount, order_id
            )
            if not payment_result['success']:
                raise PaymentError("支付失败")
            payment_method = payment_result['method']
        
        # 更新订单支付状态
        self.db.update('orders', {'order_id': order_id}, {
            'total_fee': total_amount,
            'payment_status': 'paid',
            'payment_method': payment_method,
            'updated_at': datetime.now()
        })
        
        return {'success': True, 'payment_method': payment_method}

前端实现

单车地图界面 (React Native)

import React, { useState, useEffect } from 'react';
import { View, Text, TouchableOpacity } from 'react-native';
import MapView, { Marker } from 'react-native-maps';

const BikeMapScreen = () => {
    const [userLocation, setUserLocation] = useState(null);
    const [nearbyBikes, setNearbyBikes] = useState([]);
    const [selectedBike, setSelectedBike] = useState(null);
    const [isUnlocking, setIsUnlocking] = useState(false);
    
    useEffect(() => {
        getCurrentLocation();
        const interval = setInterval(loadNearbyBikes, 10000); // 每10秒刷新
        return () => clearInterval(interval);
    }, []);
    
    const getCurrentLocation = () => {
        navigator.geolocation.getCurrentPosition(
            (position) => {
                const location = {
                    latitude: position.coords.latitude,
                    longitude: position.coords.longitude
                };
                setUserLocation(location);
                loadNearbyBikes(location);
            },
            (error) => console.error('获取位置失败:', error),
            { enableHighAccuracy: true, timeout: 20000, maximumAge: 1000 }
        );
    };
    
    const loadNearbyBikes = async (location = userLocation) => {
        if (!location) return;
        
        try {
            const response = await fetch('/api/bikes/nearby', {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify({
                    lat: location.latitude,
                    lng: location.longitude,
                    radius_km: 1.0
                })
            });
            
            const bikes = await response.json();
            setNearbyBikes(bikes);
        } catch (error) {
            console.error('加载附近单车失败:', error);
        }
    };
    
    const unlockBike = async (bikeId) => {
        if (!userLocation) {
            alert('无法获取当前位置');
            return;
        }
        
        setIsUnlocking(true);
        
        try {
            const response = await fetch('/api/bikes/unlock', {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify({
                    bike_id: bikeId,
                    unlock_lat: userLocation.latitude,
                    unlock_lng: userLocation.longitude
                })
            });
            
            const result = await response.json();
            
            if (result.success) {
                // 导航到骑行页面
                navigation.navigate('Riding', { 
                    orderId: result.order_id,
                    bikeId: bikeId
                });
            } else {
                alert(result.message || '解锁失败');
            }
        } catch (error) {
            console.error('解锁失败:', error);
            alert('解锁失败,请重试');
        } finally {
            setIsUnlocking(false);
        }
    };
    
    const getBikeIcon = (batteryLevel) => {
        if (batteryLevel > 60) return '🚲'; // 绿色
        if (batteryLevel > 30) return '🟡'; // 黄色
        return '🔴'; // 红色低电量
    };
    
    return (
        <View style={styles.container}>
            <MapView
                style={styles.map}
                region={{
                    latitude: userLocation?.latitude || 39.9042,
                    longitude: userLocation?.longitude || 116.4074,
                    latitudeDelta: 0.01,
                    longitudeDelta: 0.01,
                }}
                showsUserLocation={true}
                followsUserLocation={true}
            >
                {nearbyBikes.map(bike => (
                    <Marker
                        key={bike.bike_id}
                        coordinate={{
                            latitude: bike.lat,
                            longitude: bike.lng
                        }}
                        title={`单车 ${bike.bike_id}`}
                        description={`电量: ${bike.battery_level}% | 距离: ${bike.distance_meters}m`}
                        onPress={() => setSelectedBike(bike)}
                    >
                        <Text style={styles.bikeMarker}>
                            {getBikeIcon(bike.battery_level)}
                        </Text>
                    </Marker>
                ))}
            </MapView>
            
            {selectedBike && (
                <View style={styles.bikeInfoPanel}>
                    <Text style={styles.bikeId}>单车 {selectedBike.bike_id}</Text>
                    <Text>电量: {selectedBike.battery_level}%</Text>
                    <Text>距离: {selectedBike.distance_meters}m</Text>
                    <Text>型号: {selectedBike.bike_model}</Text>
                    
                    <TouchableOpacity
                        style={[styles.unlockButton, isUnlocking && styles.unlockingButton]}
                        onPress={() => unlockBike(selectedBike.bike_id)}
                        disabled={isUnlocking}
                    >
                        <Text style={styles.unlockButtonText}>
                            {isUnlocking ? '解锁中...' : '扫码解锁'}
                        </Text>
                    </TouchableOpacity>
                </View>
            )}
            
            <View style={styles.statsPanel}>
                <Text>附近单车: {nearbyBikes.length} 辆</Text>
                <TouchableOpacity onPress={loadNearbyBikes}>
                    <Text style={styles.refreshButton}>刷新</Text>
                </TouchableOpacity>
            </View>
        </View>
    );
};

骑行中界面

const RidingScreen = ({ route }) => {
    const { orderId, bikeId } = route.params;
    const [rideData, setRideData] = useState(null);
    const [currentLocation, setCurrentLocation] = useState(null);
    const [elapsedTime, setElapsedTime] = useState(0);
    const [estimatedFee, setEstimatedFee] = useState(0);
    
    useEffect(() => {
        loadRideData();
        startLocationTracking();
        
        const timer = setInterval(() => {
            setElapsedTime(prev => prev + 1);
            updateEstimatedFee();
        }, 1000);
        
        return () => clearInterval(timer);
    }, []);
    
    const lockBike = async () => {
        if (!currentLocation) {
            alert('无法获取当前位置');
            return;
        }
        
        try {
            const response = await fetch('/api/bikes/lock', {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify({
                    bike_id: bikeId,
                    lock_lat: currentLocation.latitude,
                    lock_lng: currentLocation.longitude
                })
            });
            
            const result = await response.json();
            
            if (result.success) {
                // 导航到结算页面
                navigation.navigate('RideComplete', {
                    orderId: orderId,
                    totalFee: result.total_fee,
                    parkingFee: result.parking_fee
                });
            } else {
                alert(result.message || '锁车失败');
            }
        } catch (error) {
            console.error('锁车失败:', error);
            alert('锁车失败,请重试');
        }
    };
    
    const formatTime = (seconds) => {
        const minutes = Math.floor(seconds / 60);
        const remainingSeconds = seconds % 60;
        return `${minutes.toString().padStart(2, '0')}:${remainingSeconds.toString().padStart(2, '0')}`;
    };
    
    return (
        <View style={styles.container}>
            <View style={styles.rideInfo}>
                <Text style={styles.bikeId}>单车 {bikeId}</Text>
                <Text style={styles.elapsedTime}>{formatTime(elapsedTime)}</Text>
                <Text style={styles.estimatedFee}>预估费用: ¥{estimatedFee.toFixed(2)}</Text>
            </View>
            
            <MapView
                style={styles.map}
                region={{
                    latitude: currentLocation?.latitude || 39.9042,
                    longitude: currentLocation?.longitude || 116.4074,
                    latitudeDelta: 0.01,
                    longitudeDelta: 0.01,
                }}
                showsUserLocation={true}
                followsUserLocation={true}
            />
            
            <View style={styles.controlPanel}>
                <TouchableOpacity
                    style={styles.lockButton}
                    onPress={lockBike}
                >
                    <Text style={styles.lockButtonText}>结束骑行</Text>
                </TouchableOpacity>
            </View>
        </View>
    );
};

这个共享单车系统设计提供了完整的车辆管理、智能调度、地理围栏和计费功能,通过IoT设备和实时数据处理确保系统的高效运营。


🎯 场景引入

你打开App,

你打开手机准备使用设计共享单车系统进阶服务。看似简单的操作背后,系统面临三大核心挑战:

  • 挑战一:高并发——如何在百万级 QPS 下保持低延迟?
  • 挑战二:高可用——如何在节点故障时保证服务不中断?
  • 挑战三:数据一致性——如何在分布式环境下保证数据正确?

📈 容量估算

假设 DAU 1000 万,人均日请求 50 次

指标数值
请求 QPS~10 万/秒
P99 延迟< 5ms
并发连接数100 万+
带宽~100 Gbps
节点数20-100
可用性99.99%
日志数据/天~1 TB

❓ 高频面试问题

Q1:共享单车系统的核心设计原则是什么?

参考正文中的架构设计部分,核心原则包括:高可用(故障自动恢复)、高性能(低延迟高吞吐)、可扩展(水平扩展能力)、一致性(数据正确性保证)。面试时需结合具体场景展开。

Q2:共享单车系统在大规模场景下的主要挑战是什么?

  1. 性能瓶颈:随着数据量和请求量增长,单节点无法承载;2) 一致性:分布式环境下的数据一致性保证;3) 故障恢复:节点故障时的自动切换和数据恢复;4) 运维复杂度:集群管理、监控、升级。

Q3:如何保证共享单车系统的高可用?

  1. 多副本冗余(至少 3 副本);2) 自动故障检测和切换(心跳 + 选主);3) 数据持久化和备份;4) 限流降级(防止雪崩);5) 多机房/多活部署。

Q4:共享单车系统的性能优化有哪些关键手段?

  1. 缓存(减少重复计算和 IO);2) 异步处理(非关键路径异步化);3) 批量操作(减少网络往返);4) 数据分片(并行处理);5) 连接池复用。

Q5:共享单车系统与同类方案相比有什么优劣势?

参考方案对比表格。选型时需考虑:团队技术栈、数据规模、延迟要求、一致性需求、运维成本。没有银弹,需根据业务场景权衡取舍。


| 方案一 | 简单实现 | 低 | 适合小规模 | | 方案二 | 中等复杂度 | 中 | 适合中等规模 | | 方案三 | 高复杂度 ⭐推荐 | 高 | 适合大规模生产环境 |

✅ 架构设计检查清单

检查项状态
缓存策略
分布式架构
监控告警
安全设计
性能优化

💡 MVP 阶段:先用单机版验证核心功能,再逐步演进到分布式架构。


⚖️ 关键 Trade-off 分析

🔴 Trade-off 1:一致性 vs 可用性

  • 强一致(CP):适用于金融交易等不能出错的场景
  • 高可用(AP):适用于社交动态等允许短暂不一致的场景
  • 本系统选择:核心路径强一致,非核心路径最终一致

🔴 Trade-off 2:同步 vs 异步

  • 同步处理:延迟低但吞吐受限,适用于核心交互路径
  • 异步处理:吞吐高但增加延迟,适用于后台计算
  • 本系统选择:核心路径同步,非核心路径异步